65 lines
1.7 KiB
Python
65 lines
1.7 KiB
Python
|
import os
|
||
|
import signal
|
||
|
import subprocess
|
||
|
from collections.abc import Iterator
|
||
|
from pathlib import Path
|
||
|
from typing import IO, Any
|
||
|
|
||
|
import pytest
|
||
|
|
||
|
_FILE = None | int | IO[Any]
|
||
|
|
||
|
|
||
|
class Command:
|
||
|
def __init__(self) -> None:
|
||
|
self.processes: list[subprocess.Popen[str]] = []
|
||
|
|
||
|
def run(
|
||
|
self,
|
||
|
command: list[str],
|
||
|
extra_env: dict[str, str] = {},
|
||
|
stdin: _FILE = None,
|
||
|
stdout: _FILE = None,
|
||
|
stderr: _FILE = None,
|
||
|
workdir: Path | None = None,
|
||
|
) -> subprocess.Popen[str]:
|
||
|
env = os.environ.copy()
|
||
|
env.update(extra_env)
|
||
|
# We start a new session here so that we can than more reliably kill all childs as well
|
||
|
p = subprocess.Popen(
|
||
|
command,
|
||
|
env=env,
|
||
|
start_new_session=True,
|
||
|
stdout=stdout,
|
||
|
stderr=stderr,
|
||
|
stdin=stdin,
|
||
|
text=True,
|
||
|
cwd=workdir,
|
||
|
)
|
||
|
self.processes.append(p)
|
||
|
return p
|
||
|
|
||
|
def terminate(self) -> None:
|
||
|
# Stop in reverse order in case there are dependencies.
|
||
|
# We just kill all processes as quickly as possible because we don't
|
||
|
# care about corrupted state and want to make tests fasts.
|
||
|
for p in reversed(self.processes):
|
||
|
try:
|
||
|
os.killpg(os.getpgid(p.pid), signal.SIGKILL)
|
||
|
except OSError:
|
||
|
pass
|
||
|
|
||
|
|
||
|
@pytest.fixture
|
||
|
def command() -> Iterator[Command]:
|
||
|
"""
|
||
|
Starts a background command. The process is automatically terminated in the end.
|
||
|
>>> p = command.run(["some", "daemon"])
|
||
|
>>> print(p.pid)
|
||
|
"""
|
||
|
c = Command()
|
||
|
try:
|
||
|
yield c
|
||
|
finally:
|
||
|
c.terminate()
|