Open
Description
Summary
pytest-xdist and pytest-cov plugins cause an internal error when used together but only on pytest version 8.4.0
Expected vs actual result
Expected pytest-xdist+pytest-cov to succeed.
See the following chart for the actual results:
⬇ pytest ♦ plugins ➡ | pytest-xdist+pytest-cov | pytest-cov | pytest-xdist |
---|---|---|---|
pytest < 8.4.0 | ✅ | ✅ | ✅ |
pytest == 8.4.0 | ❌ | ✅ | ✅ |
with the following output:
...
created: 16/16 workers
16 workers [1 item]
scheduling tests via LoadScheduling
tests/test_metadata.py::test_metadata
INTERNALERROR> def worker_internal_error(
INTERNALERROR> self, node: WorkerController, formatted_error: str
INTERNALERROR> ) -> None:
INTERNALERROR> """
INTERNALERROR> pytest_internalerror() was called on the worker.
INTERNALERROR>
INTERNALERROR> pytest_internalerror() arguments are an excinfo and an excrepr, which can't
INTERNALERROR> be serialized, so we go with a poor man's solution of raising an exception
INTERNALERROR> here ourselves using the formatted message.
INTERNALERROR> """
INTERNALERROR> self._active_nodes.remove(node)
INTERNALERROR> try:
INTERNALERROR> > assert False, formatted_error
INTERNALERROR> E AssertionError: Traceback (most recent call last):
INTERNALERROR> E File " /installdir/pluggy/_callers.py", line 43, in run_old_style_hookwrapper
INTERNALERROR> E teardown.send(result)
INTERNALERROR> E ~~~~~~~~~~~~~^^^^^^^^
INTERNALERROR> E File " /installdir/pytest_cov/plugin.py", line 324, in pytest_runtestloop
INTERNALERROR> E self.cov_controller.finish()
INTERNALERROR> E ~~~~~~~~~~~~~~~~~~~~~~~~~~^^
INTERNALERROR> E File " /installdir/pytest_cov/engine.py", line 57, in ensure_topdir_wrapper
INTERNALERROR> E return meth(self, *args, **kwargs)
INTERNALERROR> E File " /installdir/pytest_cov/engine.py", line 469, in finish
INTERNALERROR> E self.cov.save()
INTERNALERROR> E ~~~~~~~~~~~~~^^
INTERNALERROR> E File " /installdir/coverage/control.py", line 812, in save
INTERNALERROR> E data = self.get_data()
INTERNALERROR> E File " /installdir/coverage/control.py", line 893, in get_data
INTERNALERROR> E self._post_save_work()
INTERNALERROR> E ~~~~~~~~~~~~~~~~~~~~^^
INTERNALERROR> E File " /installdir/coverage/control.py", line 915, in _post_save_work
INTERNALERROR> E self._warn("No data was collected.", slug="no-data-collected")
INTERNALERROR> E ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR> E File " /installdir/coverage/control.py", line 460, in _warn
INTERNALERROR> E warnings.warn(msg, category=CoverageWarning, stacklevel=2)
INTERNALERROR> E ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR> E coverage.exceptions.CoverageWarning: No data was collected. (no-data-collected)
INTERNALERROR> E
INTERNALERROR> E During handling of the above exception, another exception occurred:
INTERNALERROR> E
INTERNALERROR> E Traceback (most recent call last):
INTERNALERROR> E File " /installdir/_pytest/main.py", line 289, in wrap_session
INTERNALERROR> E session.exitstatus = doit(config, session) or 0
INTERNALERROR> E ~~~~^^^^^^^^^^^^^^^^^
INTERNALERROR> E File " /installdir/_pytest/main.py", line 343, in _main
INTERNALERROR> E config.hook.pytest_runtestloop(session=session)
INTERNALERROR> E ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
INTERNALERROR> E File " /installdir/pluggy/_hooks.py", line 512, in __call__
INTERNALERROR> E return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
INTERNALERROR> E ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR> E File " /installdir/pluggy/_manager.py", line 120, in _hookexec
INTERNALERROR> E return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
INTERNALERROR> E ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR> E File " /installdir/pluggy/_callers.py", line 167, in _multicall
INTERNALERROR> E raise exception
INTERNALERROR> E File " /installdir/pluggy/_callers.py", line 139, in _multicall
INTERNALERROR> E teardown.throw(exception)
INTERNALERROR> E ~~~~~~~~~~~~~~^^^^^^^^^^^
INTERNALERROR> E File " /installdir/_pytest/logging.py", line 801, in pytest_runtestloop
INTERNALERROR> E return (yield) # Run all the tests.
INTERNALERROR> E ^^^^^
INTERNALERROR> E File " /installdir/pluggy/_callers.py", line 139, in _multicall
INTERNALERROR> E teardown.throw(exception)
INTERNALERROR> E ~~~~~~~~~~~~~~^^^^^^^^^^^
INTERNALERROR> E File " /installdir/_pytest/terminal.py", line 685, in pytest_runtestloop
INTERNALERROR> E result = yield
INTERNALERROR> E ^^^^^
INTERNALERROR> E File " /installdir/pluggy/_callers.py", line 152, in _multicall
INTERNALERROR> E teardown.send(result)
INTERNALERROR> E ~~~~~~~~~~~~~^^^^^^^^
INTERNALERROR> E File " /installdir/pluggy/_callers.py", line 47, in run_old_style_hookwrapper
INTERNALERROR> E _warn_teardown_exception(hook_name, hook_impl, e)
INTERNALERROR> E ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR> E File " /installdir/pluggy/_callers.py", line 73, in _warn_teardown_exception
INTERNALERROR> E warnings.warn(PluggyTeardownRaisedWarning(msg), stacklevel=6)
INTERNALERROR> E ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR> E pluggy.PluggyTeardownRaisedWarning: A plugin raised an exception during an old-style hookwrapper teardown.
INTERNALERROR> E Plugin: _cov, Hook: pytest_runtestloop
INTERNALERROR> E CoverageWarning: No data was collected. (no-data-collected)
INTERNALERROR> E For more information see https://pluggy.readthedocs.io/en/stable/api_reference.html#pluggy.PluggyTeardownRaisedWarning
INTERNALERROR> E assert False
INTERNALERROR>
INTERNALERROR> /installdir/xdist/dsession.py:232: AssertionError
ERROR: Coverage failure: total of 0 is less than fail-under=100
INTERNALERROR> Traceback (most recent call last):
INTERNALERROR> File " /installdir/_pytest/main.py", line 289, in wrap_session
INTERNALERROR> session.exitstatus = doit(config, session) or 0
INTERNALERROR> ~~~~^^^^^^^^^^^^^^^^^
INTERNALERROR> File " /installdir/_pytest/main.py", line 343, in _main
INTERNALERROR> config.hook.pytest_runtestloop(session=session)
INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
INTERNALERROR> File " /installdir/pluggy/_hooks.py", line 512, in __call__
INTERNALERROR> return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR> File " /installdir/pluggy/_manager.py", line 120, in _hookexec
INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR> File " /installdir/pluggy/_callers.py", line 167, in _multicall
INTERNALERROR> raise exception
INTERNALERROR> File " /installdir/pluggy/_callers.py", line 139, in _multicall
INTERNALERROR> teardown.throw(exception)
INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^
INTERNALERROR> File " /installdir/_pytest/logging.py", line 801, in pytest_runtestloop
INTERNALERROR> return (yield) # Run all the tests.
INTERNALERROR> ^^^^^
INTERNALERROR> File " /installdir/pluggy/_callers.py", line 139, in _multicall
INTERNALERROR> teardown.throw(exception)
INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^
INTERNALERROR> File " /installdir/_pytest/terminal.py", line 685, in pytest_runtestloop
INTERNALERROR> result = yield
INTERNALERROR> ^^^^^
INTERNALERROR> File " /installdir/pluggy/_callers.py", line 139, in _multicall
INTERNALERROR> teardown.throw(exception)
INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^
INTERNALERROR> File " /installdir/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
INTERNALERROR> return result.get_result()
INTERNALERROR> ~~~~~~~~~~~~~~~~~^^
INTERNALERROR> File " /installdir/pluggy/_result.py", line 103, in get_result
INTERNALERROR> raise exc.with_traceback(tb)
INTERNALERROR> File " /installdir/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
INTERNALERROR> res = yield
INTERNALERROR> ^^^^^
INTERNALERROR> File " /installdir/pluggy/_callers.py", line 121, in _multicall
INTERNALERROR> res = hook_impl.function(*args)
INTERNALERROR> File " /installdir/xdist/dsession.py", line 138, in pytest_runtestloop
INTERNALERROR> self.loop_once()
INTERNALERROR> ~~~~~~~~~~~~~~^^
INTERNALERROR> File " /installdir/xdist/dsession.py", line 163, in loop_once
INTERNALERROR> call(**kwargs)
INTERNALERROR> ~~~~^^^^^^^^^^
INTERNALERROR> File " /installdir/xdist/dsession.py", line 218, in worker_workerfinished
INTERNALERROR> self._active_nodes.remove(node)
INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
INTERNALERROR> KeyError: <WorkerController gw11>
Reproducer
seems to fail for any test I try given the above conditions
Versions
platform linux -- Python 3.13.3, pytest-8.4.0, pluggy-1.6.0
Config
tox config
[tool.tox]
legacy_tox_ini = """
[tox]
skipsdist = True
env_list =
dist
lint
test
[gh]
python =
3.12 = dist,lint,test
3.11 = dist,lint,test
3.10 = dist,lint,test
[testenv]
allowlist_externals =
rm
pipx
meson
python
package = wheel
deps =
uv
commands_pre =
uv pip install --color=never --no-progress OZI.build[uv,core]~=2.0.7
uv tool install --python={env_python} --force meson
commands =
meson setup {env_tmp_dir} -D{env_name}=enabled -Dtox-env-dir={env_dir}
meson compile -C {env_tmp_dir}
rm -rf {env_tmp_dir}/.gitignore
commands_post =
{env_python} -m invoke --search-root={env_tmp_dir}/ozi checkpoint --suite={env_name} --ozi {posargs}
[testenv:dist]
description = OZI distribution checkpoint
[testenv:lint]
description = OZI format/lint checkpoint
[testenv:test]
description = OZI unit tests checkpoint
commands =
meson setup {env_tmp_dir} -Dozi-blastpipe=disabled -Dtest=enabled -Dtox-env-dir={env_dir}
meson compile -C {env_tmp_dir}
rm -rf {env_tmp_dir}/.gitignore
[testenv:fix]
description = OZI project fix issues utility (black, isort, autoflake, ruff)
deps = uv
skip_install = true
commands_pre =
commands =
uv tool run --python {env_python} black -S .
uv tool run --python {env_python} isort .
uv tool run --python {env_python} autoflake -i -r .
commands_post =
[testenv:scm]
description = OZI supply chain management (setuptools_scm)
commands =
{env_python} -m setuptools_scm {posargs}
commands_post =
[testenv:invoke]
description = OZI invoke task entrypoint, for more info use "tox -e invoke -- --list"
no_package = true
commands_post =
{env_python} -m invoke --search-root={env_tmp_dir}/ozi {posargs} --ozi
"""
pytest config
[tool.pytest.ini_options] #[tool.pytest] # This will be used by pytest in the future
filterwarnings = [
"error",
"ignore:The --rsyncdir command line argument and rsyncdirs config variable are deprecated.:DeprecationWarning",
]
asyncio_mode = "auto"
log_cli = true
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
log_cli_format = "%(asctime)s [%(levelname)8s] %(name)s: %(message)s (%(filename)s:%(lineno)s)"
log_cli_level = "INFO"
My failing test
import sys
import warnings
def test_metadata() -> None:
if sys.version_info < (3, 11):
warnings.filterwarnings('ignore')
from ozi_spec import METADATA
else:
from ozi_spec import METADATA
METADATA.asdict()