steam-backlog-enforcer/steam_backlog_enforcer/tests/test_playtime_kill.py
Krzysztof kuhy Rudnicki d2565416aa Pylint gate covers tests, no suppressions
Tests are now linted by the 10.00 gate (ignore-paths dropped), with no
per-test disables. CtlServer/DaemonControl members the tests drive are
public (renamed, behaviour unchanged); SteamAPIClient._get takes its
params explicitly, as every caller already did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 12:17:07 +01:00

178 lines
6.2 KiB
Python

"""Tests for stopping the processes a playtime cutoff must end."""
from __future__ import annotations
import signal
from unittest.mock import patch
from steam_backlog_enforcer._playtime_kill import (
kill_gaming_processes,
request_steam_shutdown,
steam_and_launcher_pids,
)
PKG = "steam_backlog_enforcer._playtime_kill"
class TestRequestSteamShutdown:
"""request_steam_shutdown asks a present Steam to exit."""
def test_skips_when_steam_is_absent(self) -> None:
"""Without a steam binary nothing is run."""
with (
patch(
"steam_backlog_enforcer._playtime_kill.shutil.which", return_value=None
),
patch("steam_backlog_enforcer._playtime_kill._run") as mock_run,
):
request_steam_shutdown()
mock_run.assert_not_called()
def test_sends_shutdown(self) -> None:
"""With steam on PATH the shutdown command is sent."""
with (
patch(
"steam_backlog_enforcer._playtime_kill.shutil.which",
return_value="/usr/bin/steam",
),
patch(
"steam_backlog_enforcer._playtime_kill._run", return_value=True
) as mock_run,
):
request_steam_shutdown()
mock_run.assert_called_once_with(["/usr/bin/steam", "-shutdown"])
class TestSteamAndLauncherPids:
"""steam_and_launcher_pids finds Steam and other launchers."""
def test_unions_both_name_sets(self) -> None:
"""Steam and launcher pids are merged into one set."""
with patch(
"steam_backlog_enforcer._playtime_kill.get_pids_by_process_names",
return_value={11: "steam", 22: "lutris"},
) as mock_get:
assert steam_and_launcher_pids() == {11, 22}
names = mock_get.call_args.args[0]
assert "steam" in names
assert "lutris" in names
class TestKillGamingProcesses:
"""kill_gaming_processes signals the game tree but never us."""
def test_sigterm_by_default(self) -> None:
"""Without force the pids get SIGTERM."""
with (
patch(
"steam_backlog_enforcer._playtime_kill.descendant_pids",
return_value=set(),
),
patch(
"steam_backlog_enforcer._playtime_kill._own_process_chain",
return_value=set(),
),
patch("steam_backlog_enforcer._playtime_kill.os.kill") as mock_kill,
):
assert kill_gaming_processes({7}, force=False) == [7]
mock_kill.assert_called_once_with(7, signal.SIGTERM)
def test_sigkill_when_forced(self) -> None:
"""With force the pids get SIGKILL."""
with (
patch(
"steam_backlog_enforcer._playtime_kill.descendant_pids",
return_value=set(),
),
patch(
"steam_backlog_enforcer._playtime_kill._own_process_chain",
return_value=set(),
),
patch("steam_backlog_enforcer._playtime_kill.os.kill") as mock_kill,
):
kill_gaming_processes({7}, force=True)
mock_kill.assert_called_once_with(7, signal.SIGKILL)
def test_includes_descendants(self) -> None:
"""Descendants of the targets are signalled too."""
with (
patch(
"steam_backlog_enforcer._playtime_kill.descendant_pids",
return_value={8, 9},
),
patch(
"steam_backlog_enforcer._playtime_kill._own_process_chain",
return_value=set(),
),
patch("steam_backlog_enforcer._playtime_kill.os.kill"),
):
assert kill_gaming_processes({7}, force=False) == [7, 8, 9]
def test_never_signals_our_own_chain(self) -> None:
"""Pids in our own ancestor chain are skipped."""
with (
patch(
"steam_backlog_enforcer._playtime_kill.descendant_pids",
return_value=set(),
),
patch(
"steam_backlog_enforcer._playtime_kill._own_process_chain",
return_value={7},
),
patch("steam_backlog_enforcer._playtime_kill.os.kill") as mock_kill,
):
assert kill_gaming_processes({7, 8}, force=False) == [8]
mock_kill.assert_called_once_with(8, signal.SIGTERM)
def test_process_already_gone(self) -> None:
"""A pid that is already gone is not reported as killed."""
with (
patch(
"steam_backlog_enforcer._playtime_kill.descendant_pids",
return_value=set(),
),
patch(
"steam_backlog_enforcer._playtime_kill._own_process_chain",
return_value=set(),
),
patch(
"steam_backlog_enforcer._playtime_kill.os.kill",
side_effect=ProcessLookupError,
),
):
assert kill_gaming_processes({7}, force=False) == []
def test_permission_denied(self) -> None:
"""A pid we may not signal is not reported as killed."""
with (
patch(
"steam_backlog_enforcer._playtime_kill.descendant_pids",
return_value=set(),
),
patch(
"steam_backlog_enforcer._playtime_kill._own_process_chain",
return_value=set(),
),
patch(
"steam_backlog_enforcer._playtime_kill.os.kill",
side_effect=PermissionError,
),
):
assert kill_gaming_processes({7}, force=False) == []
def test_empty_set(self) -> None:
"""No targets means no signal."""
with (
patch(
"steam_backlog_enforcer._playtime_kill.descendant_pids",
return_value=set(),
),
patch(
"steam_backlog_enforcer._playtime_kill._own_process_chain",
return_value=set(),
),
patch("steam_backlog_enforcer._playtime_kill.os.kill") as mock_kill,
):
assert kill_gaming_processes(set(), force=False) == []
mock_kill.assert_not_called()