Coverage for vibefpmgui/processes.py: 85%
33 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-01 20:51 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-01 20:51 +0000
1"""Ending processes: a QProcess and everything it started."""
3from __future__ import annotations
5import os
6import signal
7import subprocess
9from qtpy.QtCore import QProcess
11from .console import NO_WINDOW
14def kill_process_tree(process: QProcess):
15 """Kill a QProcess *and everything it started* (fpm's compilers, the test
16 runner and the test program): killing only fpm would leave them running."""
17 pid = int(process.processId() or 0)
18 if pid > 0:
19 if os.name == "nt": 19 ↛ 20line 19 didn't jump to line 20 because the condition on line 19 was never true
20 subprocess.run(["taskkill", "/PID", str(pid), "/T", "/F"], capture_output=True, check=False, **NO_WINDOW)
21 else:
22 for child in reversed(_descendants(pid)): # deepest first
23 try:
24 os.kill(child, signal.SIGKILL)
25 except OSError:
26 pass # already gone
27 process.kill()
30def _descendants(pid: int) -> list[int]:
31 """All processes below ``pid``, parents before children (POSIX)."""
32 try:
33 out = subprocess.run(["ps", "-A", "-o", "pid=", "-o", "ppid="],
34 capture_output=True, text=True, timeout=5, check=False).stdout
35 except (OSError, subprocess.SubprocessError):
36 return []
37 children: dict[int, list[int]] = {}
38 for line in out.splitlines():
39 parts = line.split()
40 if len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit(): 40 ↛ 38line 40 didn't jump to line 38 because the condition on line 40 was always true
41 children.setdefault(int(parts[1]), []).append(int(parts[0]))
42 found, queue = [], [pid]
43 while queue:
44 for child in children.get(queue.pop(0), []):
45 found.append(child)
46 queue.append(child)
47 return found