mirror of
https://github.com/kuhyx/steam-backlog-enforcer.git
synced 2026-11-10 19:27:04 +01:00
tests: collecting test_budget_view never reads the host's leisure switch
Its module-level rules_for runs before any autouse fixture, so a live /etc/leisure-watch/leisure_from priced RULES. collection_isolated() points every switch reader at a path that cannot exist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dsh4Znwh2KnC7q6vbGpX1N
This commit is contained in:
parent
e0cf931d0e
commit
0ffc773f52
@ -10,10 +10,16 @@ Phase 2 on by writing a date to :func:`switch_file`.
|
||||
The leisure kill scan reads ``/proc`` through its own constant; it gets an
|
||||
empty directory (:func:`leisure_proc`), so no test can see -- or signal -- a
|
||||
live browser or player.
|
||||
|
||||
Module-level code runs at collection, before any fixture: it wraps itself in
|
||||
:func:`collection_isolated` instead.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import ExitStack, contextmanager
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
from unittest.mock import patch
|
||||
|
||||
@ -21,10 +27,25 @@ import pytest
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
|
||||
_SWITCH_NAME = "leisure_from"
|
||||
_PROC_NAME = "leisure_proc"
|
||||
# Every switch reader, by the module attribute it looks up at call time.
|
||||
_SWITCH_READERS = (
|
||||
"earned_time._leisure.LEISURE_FROM_PATH",
|
||||
"steam_backlog_enforcer._leisure_mode.LEISURE_FROM_PATH",
|
||||
)
|
||||
# Can never exist (/dev/null is not a directory): always the parked date.
|
||||
_NO_SWITCH = Path(os.devnull) / _SWITCH_NAME
|
||||
|
||||
|
||||
@contextmanager
|
||||
def collection_isolated() -> Iterator[None]:
|
||||
"""Point every switch reader at a file that cannot exist (no ``tmp_path``)."""
|
||||
with ExitStack() as stack:
|
||||
for target in _SWITCH_READERS:
|
||||
stack.enter_context(patch(target, _NO_SWITCH))
|
||||
yield
|
||||
|
||||
|
||||
def switch_file(tmp_path: Path) -> Path:
|
||||
@ -44,9 +65,8 @@ def _isolate_leisure(tmp_path: Path) -> Iterator[None]:
|
||||
switch.parent.mkdir(exist_ok=True)
|
||||
proc = leisure_proc(tmp_path)
|
||||
proc.mkdir(exist_ok=True)
|
||||
with (
|
||||
patch("earned_time._leisure.LEISURE_FROM_PATH", switch),
|
||||
patch("steam_backlog_enforcer._leisure_mode.LEISURE_FROM_PATH", switch),
|
||||
patch("steam_backlog_enforcer._leisure_procs._PROC", proc),
|
||||
):
|
||||
with ExitStack() as stack:
|
||||
for target in _SWITCH_READERS:
|
||||
stack.enter_context(patch(target, switch))
|
||||
stack.enter_context(patch("steam_backlog_enforcer._leisure_procs._PROC", proc))
|
||||
yield
|
||||
|
||||
@ -22,6 +22,7 @@ from steam_backlog_enforcer._budget_view import (
|
||||
from steam_backlog_enforcer._playtime_history import HistoryDay
|
||||
from steam_backlog_enforcer._playtime_state import PlaytimeState, rules_for
|
||||
from steam_backlog_enforcer.config import Config
|
||||
from steam_backlog_enforcer.tests._isolate_leisure import collection_isolated
|
||||
from steam_backlog_enforcer.tests._no_workout_http import earned_budget, fixed_budget
|
||||
|
||||
# Built at import time, so the autouse _no_workout_http fixture is not in
|
||||
@ -30,8 +31,10 @@ from steam_backlog_enforcer.tests._no_workout_http import earned_budget, fixed_b
|
||||
# have earned. Stub it explicitly so the constant is the earned budget always.
|
||||
# The held budget is pinned for the same reason: conftest's redirects are not
|
||||
# active at import either, so rules_for would read the LIVE playtime_state.json
|
||||
# and hold RULES at whatever high-water mark it records today.
|
||||
# and hold RULES at whatever high-water mark it records today. Nor would the
|
||||
# leisure switch be: the host's /etc/leisure-watch/leisure_from would price it.
|
||||
with (
|
||||
collection_isolated(),
|
||||
patch("steam_backlog_enforcer._playtime_state.held_today", return_value=0.0),
|
||||
patch(
|
||||
"steam_backlog_enforcer._playtime_state.resolve_budget",
|
||||
|
||||
32
steam_backlog_enforcer/tests/test_budget_view_collection.py
Normal file
32
steam_backlog_enforcer/tests/test_budget_view_collection.py
Normal file
@ -0,0 +1,32 @@
|
||||
"""Collecting test_budget_view never reads the host's leisure switch file."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
from typing import TYPE_CHECKING
|
||||
from unittest.mock import patch
|
||||
|
||||
from steam_backlog_enforcer._playtime_state import rules_for
|
||||
from steam_backlog_enforcer.config import Config
|
||||
from steam_backlog_enforcer.tests import test_budget_view
|
||||
from steam_backlog_enforcer.tests._no_workout_http import earned_budget
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_a_live_host_switch_does_not_leak_into_rules(tmp_path: Path) -> None:
|
||||
with (
|
||||
patch("steam_backlog_enforcer._playtime_state.held_today", return_value=0.0),
|
||||
patch(
|
||||
"steam_backlog_enforcer._playtime_state.resolve_budget",
|
||||
side_effect=earned_budget,
|
||||
),
|
||||
):
|
||||
dormant = rules_for(Config(), demo=False)
|
||||
# The host went live long ago: what the import-time read would see.
|
||||
host = tmp_path / "host_leisure_from"
|
||||
host.write_text("2000-01-01\n", encoding="utf-8")
|
||||
with patch("earned_time._leisure.LEISURE_FROM_PATH", host):
|
||||
reloaded = importlib.reload(test_budget_view)
|
||||
assert dormant == reloaded.RULES
|
||||
Loading…
Reference in New Issue
Block a user