fix(pre-commit): kill background jobs and remove their logs on interrupt

This commit is contained in:
mateo-berri 2026-08-04 21:28:46 -07:00
parent 2f36625e7f
commit a1f497c7c0
2 changed files with 67 additions and 2 deletions

View file

@ -110,6 +110,14 @@ python_checks() {
return $rc
}
on_interrupt() {
trap - INT TERM
rm -f "${python_log:-}" "${dash_log:-}" "${gen_log:-}"
kill 0 2>/dev/null
exit 130
}
trap on_interrupt INT TERM
if [ -n "$litellm_py_files" ]; then
python_log=$(mktemp)
python_checks > "$python_log" 2>&1 &

View file

@ -1,5 +1,9 @@
import os
import signal
import subprocess
import time
from collections.abc import Callable
from contextlib import suppress
from pathlib import Path
import pytest
@ -29,6 +33,11 @@ case "$*" in
lint)
[ "${STUB_FAIL:-}" = "make-lint" ] && exit 1
[ -n "${STUB_BARRIER_DIR:-}" ] && barrier_sync python "dashboard genapi"
if [ -n "${STUB_HANG_DIR:-}" ]; then
echo "$$" > "$STUB_HANG_DIR/make.pid"
touch "$STUB_HANG_DIR/make.started"
sleep 60
fi
;;
esac
exit 0
@ -99,13 +108,17 @@ def _sandbox(tmp_path: Path) -> tuple[Path, Path]:
return repo, bin_dir
def _run(repo: Path, bin_dir: Path, extra_env: dict[str, str]) -> subprocess.CompletedProcess[str]:
env = {
def _env(repo: Path, bin_dir: Path, extra_env: dict[str, str]) -> dict[str, str]:
return {
"PATH": os.pathsep.join([str(bin_dir), "/usr/bin", "/bin"]),
"HOME": str(repo.parent),
"STUB_BIN": str(bin_dir),
**extra_env,
}
def _run(repo: Path, bin_dir: Path, extra_env: dict[str, str]) -> subprocess.CompletedProcess[str]:
env = _env(repo, bin_dir, extra_env)
return subprocess.run(
[str(SCRIPT)],
cwd=repo,
@ -135,6 +148,50 @@ def test_all_blocks_passing_exits_zero(tmp_path: Path) -> None:
assert proc.returncode == 0, proc.stdout + proc.stderr
def _wait_until(predicate: Callable[[], bool], timeout_seconds: float) -> bool:
deadline = time.monotonic() + timeout_seconds
while time.monotonic() < deadline:
if predicate():
return True
time.sleep(0.05)
return predicate()
def _pid_gone(pid: int) -> bool:
try:
os.kill(pid, 0)
except ProcessLookupError:
return True
return False
def test_interrupt_kills_background_jobs_and_removes_logs(tmp_path: Path) -> None:
repo, bin_dir = _sandbox(tmp_path)
hang_dir = tmp_path / "hang"
hang_dir.mkdir()
tmp_dir = tmp_path / "tmpdir"
tmp_dir.mkdir()
extra = {"STUB_HANG_DIR": str(hang_dir), "TMPDIR": str(tmp_dir)}
proc = subprocess.Popen(
[str(SCRIPT)],
cwd=repo,
env=_env(repo, bin_dir, extra),
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True,
)
try:
assert _wait_until((hang_dir / "make.started").exists, 10)
os.killpg(proc.pid, signal.SIGINT)
assert proc.wait(timeout=10) != 0
make_pid = int((hang_dir / "make.pid").read_text())
assert _wait_until(lambda: _pid_gone(make_pid), 5)
assert list(tmp_dir.iterdir()) == []
finally:
with suppress(ProcessLookupError, PermissionError):
os.killpg(proc.pid, signal.SIGTERM)
@pytest.mark.parametrize(
("fail", "message"),
[