steam-backlog-enforcer/steam_backlog_enforcer/tests/_no_subprocess.py
Krzysztof kuhy Rudnicki 47f77f5f1e
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (push) Waiting to run
Tests / test (push) Waiting to run
Make serve re-runnable, and cover it to 100%
The serve rework was sitting untracked: a /proc-based port guard, the
start-or-defer decision, and a stale-bundle rebuild. It had no tests, and it
changed cmd_serve(_config, state) to cmd_serve(argv) without updating the one
test that called it, so the suite was red and coverage was 251 lines short.

Tests, all against fakes:
- _fake_proc builds a synthetic /proc. _NET_TCP is derived from _PROC at
  import time, so patch_proc redirects both; patching _PROC alone would have
  had these tests parse the host's real process table.
- _serve_guard is split across two test files to stay under the 250-line cap.
  os is replaced wholesale in every termination test, so no signal can reach a
  real process even if the guard's self-protection were removed.
- _web_build mocks subprocess entirely and redirects every path constant into
  tmp_path. _no_subprocess now blocks _web_build.subprocess.run too: an
  unguarded test would fire a real `npm run build`, rewriting web/dist and
  perturbing frontend_is_stale() for every later test in the run.

Source changes, both required rather than incidental:
- _serve_guard's wildcard match was a `# noqa: S104` on a "0.0.0.0" literal,
  which the no-noqa hook rejects outright. It is now the all-zero hex column
  /proc actually prints, so the dotted-quad literal never appears.
- main/__init__.py's serve dispatch arm was itself uncovered; it now has a
  test asserting the raw argv passes through unparsed.

Also drops the document.hidden guard from useBudgetPoll: it skipped the very
first load, so the budget panel sat on "Reading budget state…" forever in a
background tab, which is every tab under browser automation.

Verified: 1712 python tests, 100% branch coverage; 128 vitest, 100%. The
guard's /proc parser was checked read-only against a real listener on :8000
and resolved the same pid as `ss`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MKFzwzwRU1JD7SFLHtQBsz
2026-08-29 18:52:11 +02:00

102 lines
3.3 KiB
Python

"""Autouse guard that stops tests shelling out to real commands.
Split out of ``conftest.py`` to keep every file inside the 250-line cap;
``conftest`` imports the fixture by name, which is what registers it.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
import pytest
if TYPE_CHECKING:
from collections.abc import Iterator
@pytest.fixture(autouse=True)
def _block_real_subprocesses() -> Iterator[None]:
"""Block subprocess calls that could launch real Steam or modify system.
Individual tests that need to test subprocess behaviour should
patch the specific module's ``subprocess.run`` / ``subprocess.Popen``
themselves — their local patch will override this one.
"""
noop_run = MagicMock(return_value=MagicMock(returncode=1))
noop_popen = MagicMock()
with (
patch(
"steam_backlog_enforcer.game_install.subprocess.run",
noop_run,
),
patch(
"steam_backlog_enforcer.game_install.subprocess.Popen",
noop_popen,
),
patch(
"steam_backlog_enforcer.enforcer.subprocess.run",
noop_run,
),
patch(
"steam_backlog_enforcer.store_blocker.subprocess.run",
noop_run,
),
patch(
"steam_backlog_enforcer._total_block.subprocess.run",
noop_run,
),
patch(
"steam_backlog_enforcer._pacman.subprocess.run",
noop_run,
),
patch(
"steam_backlog_enforcer._total_block_hosts.subprocess.run",
noop_run,
),
patch(
"steam_backlog_enforcer._total_block_iptables.subprocess.run",
noop_run,
),
# library_hider no longer spawns: its launch/process code lives in
# _steam_launch / _steam_process, and _steam_client starts the client.
# Every module that can spawn must be patched here, or a test could
# fire a real `sudo -u kuhy steam`.
patch(
"steam_backlog_enforcer._steam_launch.subprocess.run",
noop_run,
),
patch(
"steam_backlog_enforcer._steam_launch.subprocess.Popen",
noop_popen,
),
patch(
"steam_backlog_enforcer._steam_process.subprocess.Popen",
noop_popen,
),
patch(
"steam_backlog_enforcer._steam_client.subprocess.run",
noop_run,
),
patch(
"steam_backlog_enforcer._steam_client.subprocess.Popen",
noop_popen,
),
# The single most important patch in this file. _playtime_block._run
# executes `mount --bind` against paths that, unpatched, are the real
# /usr/bin/steam. A test that reached the real subprocess would mask
# the user's actual Steam install with a refusal stub.
patch(
"steam_backlog_enforcer._playtime_run.subprocess.run",
noop_run,
),
# A real `npm run build` here would rewrite web/dist/index.html, which
# then perturbs frontend_is_stale() for every later test in the run.
patch(
"steam_backlog_enforcer._web_build.subprocess.run",
noop_run,
),
):
yield