1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
|
import asyncio
import os
import subprocess
from importlib.metadata import version
from pathlib import Path
from typing import TYPE_CHECKING, AsyncGenerator
import pytest
import pytest_asyncio
from packaging.specifiers import SpecifierSet
from cockpit._vendor.systemd_ctypes import EventLoopPolicy
def any_subprocesses() -> bool:
# Make sure we don't leak subprocesses
try:
os.waitid(os.P_ALL, -1, os.WEXITED | os.WNOHANG | os.WNOWAIT)
except ChildProcessError:
return False # good !
else:
return True # at least one process (or zombie) still waitable
# some issues:
# - even sourcing this will break old python versions in ways we don't care to fix
# - even sourcing this requires aiohttp, which isn't available everywhere
# - there are general issues running under tox which need investigation
# - this also requires a bots/ checkout which we don't have in distros
if 'NO_QUNIT' in os.environ:
@pytest.hookimpl
def pytest_ignore_collect(collection_path: Path) -> bool:
return collection_path.name == "test_browser.py"
# if m.image in ['ubuntu-2204', '🙃']:
if not TYPE_CHECKING and version("pytest") not in SpecifierSet(">=7.0"):
@pytest.hookimpl
def pytest_ignore_collect(path: object) -> bool:
return Path(str(path)).name == "test_browser.py"
else:
pytest_plugins = "js_coverage"
@pytest.fixture
# AbstractEventLoopPolicy is deprecated in 3.14 and will be removed in 3.16
def event_loop_policy() -> asyncio.AbstractEventLoopPolicy: # type: ignore[name-defined]
return EventLoopPolicy()
# if m.image in ['ubuntu-2204', 'ubuntu-2404', '🙃']:
if version("pytest-asyncio") not in SpecifierSet(">=0.22"):
# Compatibility with pre-`event_loop_policy` versions of pytest-asyncio
@pytest.fixture(autouse=True)
def event_loop() -> asyncio.AbstractEventLoop:
return EventLoopPolicy().new_event_loop()
@pytest_asyncio.fixture(autouse=True)
async def _check_settled() -> AsyncGenerator[None, None]:
yield
current_task = asyncio.current_task()
assert current_task is not None
event_loop = asyncio.get_running_loop()
# Let all tasks and subprocesses run to completion
for _ in range(200):
all_tasks = asyncio.all_tasks(loop=event_loop) - {current_task}
if not (all_tasks or any_subprocesses()):
break
await asyncio.sleep(0.005)
# No tasks left
assert asyncio.all_tasks(loop=event_loop) == {current_task}
# No subprocesses left
if any_subprocesses():
# Bad news. Show some helpful output.
subprocess.run(['ps', 'f', f'--pid={os.getpid()}', f'--ppid={os.getpid()}'])
# clear it out for the sake of the other tests
subprocess.run(['pkill', '-9', '-P', f'{os.getpid()}'])
try:
for _ in range(100): # zombie vacuum
os.wait()
except ChildProcessError:
pass
pytest.fail('Some subprocesses still running!')
|