python-pytest-harvest icon indicating copy to clipboard operation
python-pytest-harvest copied to clipboard

EOFError during pytest session finish with pytest_xdist plugin

Open junuMoon opened this issue 1 year ago • 3 comments

cmd

pytest tests/test_model_follow_character_description.py -n 4

Env

  • OS: Linux
  • Python Version: 3.12.3
  • pytest Version: 8.1.1
  • pytest Plugins:
    • anyio-4.3.0
    • asyncio-0.23.6
    • xdist-3.6.1
    • repeat-0.9.3
    • deepeval-0.21.36
    • harvest-1.10.5

Error Traceback

============================================= test session starts ==============================================
platform linux -- Python 3.12.3, pytest-8.1.1, pluggy-1.5.0
rootdir: /mnt/raid/fran/kai
configfile: pytest.ini
plugins: anyio-4.3.0, asyncio-0.23.6, xdist-3.6.1, repeat-0.9.3, deepeval-0.21.36, harvest-1.10.5
asyncio: mode=Mode.STRICT
initialized: 4/4 workers/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/deepeval/__init__.py:42: UserWarning: You are using deepeval version 0.21.36, however version 0.21.42 is available. You should consider upgrading via the "pip install --upgrade deepeval" command.
  warnings.warn(
/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/deepeval/__init__.py:42: UserWarning: You are using deepeval version 0.21.36, however version 0.21.42 is available. You should consider upgrading via the "pip install --upgrade deepeval" command.
  warnings.warn(
/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/deepeval/__init__.py:42: UserWarning: You are using deepeval version 0.21.36, however version 0.21.42 is available. You should consider upgrading via the "pip install --upgrade deepeval" command.
  warnings.warn(
/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/deepeval/__init__.py:42: UserWarning: You are using deepeval version 0.21.36, however version 0.21.42 is available. You should consider upgrading via the "pip install --upgrade deepeval" command.
  warnings.warn(
4 workers [4 items]     
....                                                                                                     [100%]Running teardown with pytest sessionfinish...
Traceback (most recent call last):
  File "/mnt/raid/fran/miniconda3/bin/pytest", line 8, in <module>
    sys.exit(console_main())
             ^^^^^^^^^^^^^^
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/_pytest/config/__init__.py", line 197, in console_main
    code = main()
           ^^^^^^
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/_pytest/config/__init__.py", line 174, in main
    ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main(
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/pluggy/_callers.py", line 139, in _multicall
    raise exception.with_traceback(exception.__traceback__)
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/pluggy/_callers.py", line 103, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/_pytest/main.py", line 332, in pytest_cmdline_main
    return wrap_session(config, _main)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/_pytest/main.py", line 320, in wrap_session
    config.hook.pytest_sessionfinish(
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/pluggy/_callers.py", line 182, in _multicall
    return outcome.get_result()
           ^^^^^^^^^^^^^^^^^^^^
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/pluggy/_result.py", line 100, in get_result
    raise exc.with_traceback(exc.__traceback__)
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/pluggy/_callers.py", line 167, in _multicall
    teardown.throw(outcome._exception)
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/_pytest/logging.py", line 871, in pytest_sessionfinish
    return (yield)
            ^^^^^
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/pluggy/_callers.py", line 167, in _multicall
    teardown.throw(outcome._exception)
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/_pytest/terminal.py", line 866, in pytest_sessionfinish
    result = yield
             ^^^^^
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/pluggy/_callers.py", line 167, in _multicall
    teardown.throw(outcome._exception)
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/_pytest/warnings.py", line 140, in pytest_sessionfinish
    return (yield)
            ^^^^^
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/pluggy/_callers.py", line 103, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/mnt/raid/fran/kai/tests/conftest.py", line 82, in pytest_sessionfinish
    session_results_df = get_session_results_df(session)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/pytest_harvest/plugin.py", line 246, in get_session_results_df
    possibly_restore_xdist_workers_structs(session_or_request)
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/pytest_harvest/plugin.py", line 476, in possibly_restore_xdist_workers_structs
    workers_saved_material = session.config.hook.pytest_harvest_xdist_load()
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/mnt/raid/fran/miniconda3/lib/python3.12/site-packages/pluggy/_manager.py", line

junuMoon avatar May 16 '24 01:05 junuMoon

I found out that cloudpickle can solve this issue. I recommend the library will switch to it :) cc @smarie

# conftest.py
import cloudpickle  # type: ignore
from typing import OrderedDict, Dict, Any
from logging import warning
from pathlib import Path
from shutil import rmtree

# ## Xdist harvesting using cloudpickle instead of pytest-harvest's default pickling
# ## This is useful when the harvested objects are not serializable by pytest-harvest's default pickling.
RESULTS_PATH = Path('./.xdist_harvested/')


def pytest_harvest_xdist_init() -> bool:
    if RESULTS_PATH.exists():
        rmtree(RESULTS_PATH)
    RESULTS_PATH.mkdir(exist_ok=False)
    return True


def pytest_harvest_xdist_worker_dump(worker_id: str, session_items: Any, fixture_store: OrderedDict[Any, Any]) -> bool:
    with open(RESULTS_PATH / f'{worker_id}.pkl', 'wb') as f:
        try:
            cloudpickle.dump((session_items, fixture_store), f)
        except Exception as e:
            warning(f"Error while pickling worker {worker_id}'s harvested results: [{e.__class__}] {e}")
    return True


def pytest_harvest_xdist_load() -> Dict[str, Any]:
    workers_saved_material = dict()
    for pkl_file in RESULTS_PATH.glob('*.pkl'):
        wid = pkl_file.stem
        with pkl_file.open('rb') as f:
            workers_saved_material[wid] = cloudpickle.load(f)
    return workers_saved_material


def pytest_harvest_xdist_cleanup() -> bool:
    rmtree(RESULTS_PATH)
    return True

AlmogBaku avatar Sep 22 '24 17:09 AlmogBaku

Hi both, thanks a lot @junuMoon for finding this issue and @AlmogBaku for finding a solution !

I cannot reproduce the issue on my side, so @junuMoon can you please

  • run your code again and confirm that the issue still exists with the latest version of pytest-harvest and pytest-xdist ?
  • and if it still exists, try to checkout and install branch #75 and confirm that it solves the issue for you ?

Thanks a lot !

smarie avatar Sep 30 '24 10:09 smarie

@junuMoon or @AlmogBaku any update on this ?

  • can you reproduce the initial issue still ?
  • if so, does PR #75 fix it for you ?

smarie avatar Jun 09 '25 08:06 smarie