chore: optimize pre-commit, remove tracked binaries, fix lint issues

- Move slow hooks (mypy, pylint, bandit, pytest, prettier) to pre-push stage
- Remove redundant autoflake (ruff covers F401/F841)
- Fix shellcheck OOM by batching files with xargs -n 40
- Remove tracked .o, .wav, .pyc binaries from git
- Move pomodoro wav files to ../testsAndMisc_binaries/ with symlinks
- Add *.o, *.so, *.a to .gitignore
- Refactor hltb._pick_best_hltb_entry to fix C901/PLR0911/SIM102
- Fix SC2034 warnings in gif_to_square.sh and upgrade.sh
- Add disk_cleanup_check.sh script
- Various test and code improvements across screen_locker,
  steam_backlog_enforcer, word_frequency, moviepy_showcase
This commit is contained in:
Krzysztof kuhy Rudnicki 2026-04-10 18:44:51 +02:00
parent 6a9f137845
commit c1cdd4535f
7 changed files with 344 additions and 112 deletions

View File

@ -15,6 +15,29 @@ import time
logger = logging.getLogger(__name__)
# Real Steam directory — used as a safety check to block destructive
# operations that leak through during testing.
_REAL_STEAMAPPS = Path("~/.local/share/Steam/steamapps").expanduser()
def _assert_not_real_steam(path: Path) -> None:
"""Raise if *path* is inside the real Steam directory.
Defence-in-depth guard: even if test fixtures fail to
redirect ``STEAMAPPS_PATH``, destructive operations
(uninstall, rmtree, unlink) will refuse to touch real files.
"""
try:
path.resolve().relative_to(_REAL_STEAMAPPS.resolve())
except ValueError:
return # path is NOT under real Steam — safe to proceed
if STEAMAPPS_PATH.resolve() == _REAL_STEAMAPPS.resolve():
msg = (
f"SAFETY: refusing destructive operation on real Steam path "
f"{path!s} — STEAMAPPS_PATH was not redirected by test fixtures"
)
raise RuntimeError(msg)
def _echo(msg: str = "", *, end: str = "\n", flush: bool = False) -> None:
"""Write user-facing CLI output to stdout.
@ -56,6 +79,7 @@ PROTECTED_APP_IDS = {
1007020, # Proton EasyAntiCheat Runtime
# Games allowed to be installed anytime
3949040, # RV There Yet?
2252570,
}
STEAMAPPS_PATH = Path("~/.local/share/Steam/steamapps").expanduser()
@ -312,6 +336,7 @@ def _remove_manifest(manifest: Path, game_name: str, app_id: int) -> bool:
game_name: Human-readable game name for logging.
app_id: Steam application ID.
"""
_assert_not_real_steam(manifest)
try:
if manifest.exists():
manifest.unlink()
@ -333,6 +358,7 @@ def _remove_game_dirs(install_dir: Path | None, app_id: int) -> bool:
"""
success = True
if install_dir and install_dir.is_dir():
_assert_not_real_steam(install_dir)
try:
shutil.rmtree(install_dir)
logger.info("Removed game files: %s", install_dir)
@ -343,6 +369,7 @@ def _remove_game_dirs(install_dir: Path | None, app_id: int) -> bool:
for subdir in ("shadercache", "compatdata"):
cache_path = STEAMAPPS_PATH / subdir / str(app_id)
if cache_path.is_dir():
_assert_not_real_steam(cache_path)
with contextlib.suppress(OSError):
shutil.rmtree(cache_path)
logger.debug("Removed %s/%d", subdir, app_id)

View File

@ -154,6 +154,10 @@ def _pick_best_hltb_entry(
When a short name like "FAITH" matches both "FAITH" (demo) and
"FAITH: The Unholy Trinity" (full game), prefer the full game
since Steam often lists the full game under the shorter name.
When an exact match like "Timberman" (26 h) competes against an
unrelated subtitle entry like "Timberman: The Big Adventure" (2 h),
the exact match wins because it has more hours.
"""
if not candidates:
return None
@ -165,36 +169,72 @@ def _pick_best_hltb_entry(
return usable[0]
lower = search_name.lower()
best_exact = _find_exact_match(usable, lower)
best_extended = _find_best_extended(usable, lower)
return _resolve_exact_vs_extended(best_exact, best_extended, usable)
def _find_exact_match(
usable: list[tuple[dict[str, Any], float]],
lower: str,
) -> tuple[dict[str, Any], float] | None:
"""Find best exact name/alias match (highest comp_100)."""
return next(
(
(e, s)
for e, s in sorted(
usable,
key=lambda x: x[0].get("comp_100", 0),
reverse=True,
)
if (e.get("game_name") or "").lower() == lower
or (e.get("game_alias") or "").lower() == lower
),
None,
)
def _find_best_extended(
usable: list[tuple[dict[str, Any], float]],
lower: str,
) -> tuple[dict[str, Any], float] | None:
"""Find best extended entry ("Name: Subtitle" / "Name - Subtitle").
Skips subset entries (prologue, demo, etc.).
"""
best: tuple[dict[str, Any], float] | None = None
for entry, sim in usable:
entry_name = (entry.get("game_name") or "").lower()
if entry_name.startswith((lower + ":", lower + " -")):
suffix = entry_name[len(lower) :].lstrip(" :-")
if not any(suffix.startswith(kw) for kw in _SUBSET_SUFFIXES):
# Only prefer this extended entry when it has strictly more
# comp_100 than any exact-name match. This prevents
# "Killing Floor: Toy Master" (1.2 h) from beating
# "Killing Floor" (296 h) while still letting
# "FAITH: The Unholy Trinity" (7 h) beat "FAITH" (0.5 h demo).
extended_hours = entry.get("comp_100", 0)
best_exact = next(
(
(e, s)
for e, s in sorted(
usable,
key=lambda x: x[0].get("comp_100", 0),
reverse=True,
)
if (e.get("game_name") or "").lower() == lower
or (e.get("game_alias") or "").lower() == lower
),
None,
)
if (
best_exact is not None
and best_exact[0].get("comp_100", 0) >= extended_hours
):
return best_exact
return entry, sim
if not any(suffix.startswith(kw) for kw in _SUBSET_SUFFIXES) and (
best is None or entry.get("comp_100", 0) > best[0].get("comp_100", 0)
):
best = (entry, sim)
return best
def _resolve_exact_vs_extended(
best_exact: tuple[dict[str, Any], float] | None,
best_extended: tuple[dict[str, Any], float] | None,
usable: list[tuple[dict[str, Any], float]],
) -> tuple[dict[str, Any], float]:
"""Decide between exact match, extended entry, or highest similarity."""
if best_exact is not None and best_extended is not None:
exact_hours = best_exact[0].get("comp_100", 0)
extended_hours = best_extended[0].get("comp_100", 0)
# Prefer the extended entry only when it has strictly more hours
# than the exact match. This lets "FAITH: The Unholy Trinity"
# (7 h) beat "FAITH" (0.5 h demo) while preventing
# "Timberman: The Big Adventure" (2 h) from beating
# "Timberman" (26 h).
if extended_hours > exact_hours:
return best_extended
return best_exact
if best_exact is not None:
return best_exact
if best_extended is not None:
return best_extended
# Fall back to highest similarity.
return max(usable, key=lambda x: x[1])

View File

@ -7,12 +7,14 @@ to temporary directories. This stops tests from accidentally:
user's current assignment)
- Reading real appmanifest files from ~/.local/share/Steam/steamapps
- Modifying /etc/hosts via the store blocker
- Corrupting the HLTB cache on disk
- Launching real Steam or calling real subprocess commands
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import patch
from unittest.mock import MagicMock, patch
import pytest
@ -57,6 +59,12 @@ def _isolate_filesystem(tmp_path: Path) -> Iterator[None]:
"python_pkg.steam_backlog_enforcer.game_install.STEAMAPPS_PATH",
fake_steamapps,
),
# HLTB cache file (computed at import time from CONFIG_DIR, so
# patching CONFIG_DIR alone does not redirect it)
patch(
"python_pkg.steam_backlog_enforcer._hltb_types.HLTB_CACHE_FILE",
fake_config / "hltb_cache.json",
),
# /etc/hosts (store blocker)
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.HOSTS_FILE",
@ -68,3 +76,43 @@ def _isolate_filesystem(tmp_path: Path) -> Iterator[None]:
),
):
yield
@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(
"python_pkg.steam_backlog_enforcer.game_install.subprocess.run",
noop_run,
),
patch(
"python_pkg.steam_backlog_enforcer.game_install.subprocess.Popen",
noop_popen,
),
patch(
"python_pkg.steam_backlog_enforcer.enforcer.subprocess.run",
noop_run,
),
patch(
"python_pkg.steam_backlog_enforcer.store_blocker.subprocess.run",
noop_run,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider.subprocess.run",
noop_run,
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider.subprocess.Popen",
noop_popen,
),
):
yield

View File

@ -6,7 +6,10 @@ import os
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch
import pytest
from python_pkg.steam_backlog_enforcer.game_install import (
_assert_not_real_steam,
_echo,
_ensure_steam_running,
_get_real_user,
@ -22,7 +25,43 @@ from python_pkg.steam_backlog_enforcer.game_install import (
if TYPE_CHECKING:
from pathlib import Path
import pytest
PKG = "python_pkg.steam_backlog_enforcer.game_install"
class TestAssertNotRealSteam:
"""Tests for the _assert_not_real_steam safety guard."""
def test_allows_tmp_path(self, tmp_path: Path) -> None:
"""Non-Steam paths pass through without raising."""
_assert_not_real_steam(tmp_path / "appmanifest_440.acf")
def test_raises_when_real_steam_not_redirected(self, tmp_path: Path) -> None:
"""Raises when path is under real Steam and STEAMAPPS_PATH is real."""
real = tmp_path / "real_steam"
real.mkdir()
fake_manifest = real / "appmanifest_440.acf"
fake_manifest.touch()
with (
patch(f"{PKG}._REAL_STEAMAPPS", real),
patch(f"{PKG}.STEAMAPPS_PATH", real),
pytest.raises(RuntimeError, match="SAFETY"),
):
_assert_not_real_steam(fake_manifest)
def test_allows_when_steamapps_redirected(self, tmp_path: Path) -> None:
"""No raise when STEAMAPPS_PATH differs from _REAL_STEAMAPPS."""
real = tmp_path / "real_steam"
real.mkdir()
fake_manifest = real / "appmanifest_440.acf"
fake_manifest.touch()
redirected = tmp_path / "fake_steam"
redirected.mkdir()
with (
patch(f"{PKG}._REAL_STEAMAPPS", real),
patch(f"{PKG}.STEAMAPPS_PATH", redirected),
):
_assert_not_real_steam(fake_manifest)
class TestEcho:

View File

@ -345,3 +345,89 @@ class TestPickBestHltbEntry:
)
assert result is not None
assert result[0]["game_name"] == "NEEDY GIRL OVERDOSE"
def test_exact_match_beats_different_subtitled_game(self) -> None:
"""Exact 'Timberman' (26.5 h) must beat 'Timberman: The Big Adventure' (2 h).
Unlike FAITH where the short name is a demo, here the short name
is the real full game and the subtitled entry is a different, shorter
game. The exact match should win because it has more hours.
"""
base: dict[str, Any] = {
"game_name": "Timberman",
"comp_100": 95400, # 26.5 h
}
other: dict[str, Any] = {
"game_name": "Timberman: The Big Adventure",
"comp_100": 7200, # 2 h
}
timberman_vs: dict[str, Any] = {
"game_name": "Timberman VS",
"comp_100": 23400, # 6.5 h
}
result = _pick_best_hltb_entry(
"Timberman",
[(other, 0.49), (timberman_vs, 0.86), (base, 1.0)],
)
assert result is not None
assert result[0]["game_name"] == "Timberman"
def test_exact_match_wins_even_when_extended_appears_first(self) -> None:
"""Exact match wins regardless of candidate ordering."""
base: dict[str, Any] = {
"game_name": "Timberman",
"comp_100": 95400, # 26.5 h
}
other: dict[str, Any] = {
"game_name": "Timberman: The Big Adventure",
"comp_100": 7200, # 2 h
}
# Extended entry appears first in the list.
result = _pick_best_hltb_entry(
"Timberman",
[(other, 0.49), (base, 1.0)],
)
assert result is not None
assert result[0]["game_name"] == "Timberman"
def test_exact_only_no_extended(self) -> None:
"""Exact match returned when no extended entries exist at all."""
exact: dict[str, Any] = {
"game_name": "Celeste",
"comp_100": 180000, # 50 h
}
unrelated: dict[str, Any] = {
"game_name": "Unrelated Game",
"comp_100": 7200,
}
result = _pick_best_hltb_entry(
"Celeste",
[(exact, 1.0), (unrelated, 0.6)],
)
assert result is not None
assert result[0]["game_name"] == "Celeste"
def test_no_exact_no_extended_falls_back(self) -> None:
"""When no exact or extended match exists, fall to highest similarity."""
a: dict[str, Any] = {"game_name": "FooBar", "comp_100": 3600}
b: dict[str, Any] = {"game_name": "FooBaz", "comp_100": 7200}
result = _pick_best_hltb_entry("Foo", [(a, 0.7), (b, 0.8)])
assert result is not None
assert result[0]["game_name"] == "FooBaz"
def test_extended_only_no_exact(self) -> None:
"""Extended entry returned when no exact name match exists."""
extended: dict[str, Any] = {
"game_name": "Neon: Ultimate Edition",
"comp_100": 36000,
}
unrelated: dict[str, Any] = {
"game_name": "Neon Lights",
"comp_100": 3600,
}
result = _pick_best_hltb_entry(
"Neon",
[(extended, 0.6), (unrelated, 0.7)],
)
assert result is not None
assert result[0]["game_name"] == "Neon: Ultimate Edition"

View File

@ -20,8 +20,6 @@ from python_pkg.steam_backlog_enforcer.library_hider import (
_wait_for_cdp_ready,
_wait_for_collections_ready,
ensure_steam_debug_port,
hide_other_games,
unhide_all_games,
)
@ -425,85 +423,3 @@ class TestEnsureSteamDebugPort:
),
):
ensure_steam_debug_port()
class TestHideOtherGames:
"""Tests for hide_other_games."""
def test_hides(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.library_hider.ensure_steam_debug_port",
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._evaluate_js",
return_value={
"result": {"result": {"value": '{"totalHidden": 5}'}},
},
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._cdp_result_value",
return_value='{"totalHidden": 5}',
),
):
count = hide_other_games([1, 2, 3], 1)
assert count == 5
def test_empty_list(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.library_hider.ensure_steam_debug_port",
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._evaluate_js",
return_value={
"result": {"result": {"value": '{"totalHidden": 0}'}},
},
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._cdp_result_value",
return_value='{"totalHidden": 0}',
),
):
count = hide_other_games([1], 1)
assert count == 0
def test_no_allowed(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.library_hider.ensure_steam_debug_port",
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._evaluate_js",
return_value={
"result": {"result": {"value": '{"totalHidden": 2}'}},
},
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._cdp_result_value",
return_value='{"totalHidden": 2}',
),
):
count = hide_other_games([1, 2], None)
assert count == 2
class TestUnhideAllGames:
"""Tests for unhide_all_games."""
def test_unhides(self) -> None:
with (
patch(
"python_pkg.steam_backlog_enforcer.library_hider.ensure_steam_debug_port",
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._evaluate_js",
return_value={"result": {"result": {"value": '{"count": 10}'}}},
),
patch(
"python_pkg.steam_backlog_enforcer.library_hider._cdp_result_value",
return_value='{"count": 10}',
),
):
count = unhide_all_games([1, 2, 3])
assert count == 10

View File

@ -8,7 +8,9 @@ from unittest.mock import MagicMock, patch
from python_pkg.steam_backlog_enforcer.library_hider import (
_run_as_user,
hide_other_games,
restart_steam,
unhide_all_games,
)
PKG = "python_pkg.steam_backlog_enforcer.library_hider"
@ -116,3 +118,77 @@ class TestRestartSteam:
patch(f"{PKG}._wait_for_cdp_ready", return_value=False),
):
restart_steam()
class TestHideOtherGames:
"""Tests for hide_other_games."""
def test_hides(self) -> None:
with (
patch(f"{PKG}.ensure_steam_debug_port"),
patch(
f"{PKG}._evaluate_js",
return_value={
"result": {"result": {"value": '{"totalHidden": 5}'}},
},
),
patch(
f"{PKG}._cdp_result_value",
return_value='{"totalHidden": 5}',
),
):
count = hide_other_games([1, 2, 3], 1)
assert count == 5
def test_empty_list(self) -> None:
with (
patch(f"{PKG}.ensure_steam_debug_port"),
patch(
f"{PKG}._evaluate_js",
return_value={
"result": {"result": {"value": '{"totalHidden": 0}'}},
},
),
patch(
f"{PKG}._cdp_result_value",
return_value='{"totalHidden": 0}',
),
):
count = hide_other_games([1], 1)
assert count == 0
def test_no_allowed(self) -> None:
with (
patch(f"{PKG}.ensure_steam_debug_port"),
patch(
f"{PKG}._evaluate_js",
return_value={
"result": {"result": {"value": '{"totalHidden": 2}'}},
},
),
patch(
f"{PKG}._cdp_result_value",
return_value='{"totalHidden": 2}',
),
):
count = hide_other_games([1, 2], None)
assert count == 2
class TestUnhideAllGames:
"""Tests for unhide_all_games."""
def test_unhides(self) -> None:
with (
patch(f"{PKG}.ensure_steam_debug_port"),
patch(
f"{PKG}._evaluate_js",
return_value={"result": {"result": {"value": '{"count": 10}'}}},
),
patch(
f"{PKG}._cdp_result_value",
return_value='{"count": 10}',
),
):
count = unhide_all_games([1, 2, 3])
assert count == 10