2023-08-09 14:38:08 +00:00
|
|
|
import os
|
|
|
|
import signal
|
|
|
|
import subprocess
|
2023-11-29 11:40:48 +00:00
|
|
|
from collections.abc import Iterator
|
2023-10-24 14:44:54 +00:00
|
|
|
from pathlib import Path
|
2023-11-29 11:40:48 +00:00
|
|
|
from typing import IO, Any
|
2023-08-09 14:38:08 +00:00
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
2023-11-29 14:59:24 +00:00
|
|
|
_FILE = None | int | IO[Any]
|
2023-08-09 14:38:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Command:
|
|
|
|
def __init__(self) -> None:
|
2023-11-29 11:40:48 +00:00
|
|
|
self.processes: list[subprocess.Popen[str]] = []
|
2023-08-09 14:38:08 +00:00
|
|
|
|
|
|
|
def run(
|
|
|
|
self,
|
2023-11-29 11:40:48 +00:00
|
|
|
command: list[str],
|
|
|
|
extra_env: dict[str, str] = {},
|
2023-08-09 14:38:08 +00:00
|
|
|
stdin: _FILE = None,
|
|
|
|
stdout: _FILE = None,
|
|
|
|
stderr: _FILE = None,
|
2023-11-29 11:40:48 +00:00
|
|
|
workdir: Path | None = None,
|
2023-08-09 14:38:08 +00:00
|
|
|
) -> 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,
|
2023-10-24 14:44:54 +00:00
|
|
|
cwd=workdir,
|
2023-08-09 14:38:08 +00:00
|
|
|
)
|
|
|
|
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()
|