From 0497a311e2e32658e57dad9c93ac167dddf6b8f9 Mon Sep 17 00:00:00 2001 From: BANANASJIM Date: Tue, 16 Jun 2026 02:31:11 -0700 Subject: [PATCH] test: add RDC_DATA_DIR seam and centralize unit/e2e home isolation --- src/rdc/_platform.py | 9 ++- tests/e2e/conftest.py | 68 +++++++++++++++++---- tests/e2e/e2e_helpers.py | 12 ++++ tests/unit/conftest.py | 13 ++++ tests/unit/test_android_commands.py | 5 -- tests/unit/test_capture_control.py | 6 -- tests/unit/test_cli_session_flag.py | 1 - tests/unit/test_keep_remote.py | 6 -- tests/unit/test_platform.py | 63 ++++++++++++++++++- tests/unit/test_remote_commands.py | 5 -- tests/unit/test_remote_core.py | 6 +- tests/unit/test_remote_setup.py | 6 -- tests/unit/test_remote_state.py | 5 -- tests/unit/test_remote_status_disconnect.py | 6 -- tests/unit/test_session_commands.py | 13 +--- tests/unit/test_session_service.py | 9 --- tests/unit/test_session_state.py | 4 -- tests/unit/test_shader_preload.py | 2 - tests/unit/test_split_core.py | 1 - tests/unit/test_target_state.py | 7 --- 20 files changed, 157 insertions(+), 90 deletions(-) diff --git a/src/rdc/_platform.py b/src/rdc/_platform.py index fa64c676..c55ec9e8 100644 --- a/src/rdc/_platform.py +++ b/src/rdc/_platform.py @@ -19,7 +19,14 @@ def data_dir() -> Path: - """Return the per-user data directory for rdc.""" + """Return the per-user data directory for rdc. + + Honours the ``RDC_DATA_DIR`` environment override (mirrors ``RDC_SESSION``); + when unset, falls back to the per-user home-based default. + """ + override = os.environ.get("RDC_DATA_DIR") + if override: + return Path(override) if _WIN: base = os.environ.get("LOCALAPPDATA", str(Path.home())) return Path(base) / "rdc" diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index 5a995c76..85631ebc 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -6,12 +6,14 @@ from __future__ import annotations +import json import logging import os import uuid from collections.abc import Generator from pathlib import Path +import e2e_helpers import pytest from e2e_helpers import ( DYNAMIC_RENDERING, @@ -25,6 +27,8 @@ self_capture, ) +from rdc import _platform + # --------------------------------------------------------------------------- # Session-scoped fixtures # --------------------------------------------------------------------------- @@ -33,6 +37,37 @@ _DISCOVER_SESSION = "e2e_discover" +def _reap_daemons(data_dir: Path) -> None: + """Terminate any daemons still recorded under *data_dir* (best effort).""" + for session_file in data_dir.glob("sessions/*.json"): + try: + pid = int(json.loads(session_file.read_text()).get("pid", 0)) + except (OSError, ValueError, TypeError, json.JSONDecodeError): + continue + if pid > 0 and _platform.is_pid_alive(pid): + _platform.terminate_process_tree(pid) + + +@pytest.fixture(scope="session", autouse=True) +def _isolate_e2e_data_dir( + tmp_path_factory: pytest.TempPathFactory, +) -> Generator[Path, None, None]: + """Redirect every e2e CLI subprocess to a session-scoped tmp data dir. + + Sets ``RDC_DATA_DIR`` for all ``rdc`` subprocesses so the real CLI never + writes the developer's ~/.rdc, and reaps any surviving daemons on teardown. + Scope is session (not function): an e2e daemon is shared across a module's + tests, so per-test isolation would tear shared sessions down mid-run. + """ + data_dir = tmp_path_factory.mktemp("e2e_data") / ".rdc" + e2e_helpers.SUBPROCESS_ENV["RDC_DATA_DIR"] = str(data_dir) + try: + yield data_dir + finally: + _reap_daemons(data_dir) + e2e_helpers.SUBPROCESS_ENV.pop("RDC_DATA_DIR", None) + + @pytest.fixture(scope="session") def captured_rdc(tmp_path_factory: pytest.TempPathFactory) -> Generator[Path, None, None]: """Self-capture vkcube or fall back to pre-recorded fixture.""" @@ -56,10 +91,9 @@ def captured_rdc(tmp_path_factory: pytest.TempPathFactory) -> Generator[Path, No def capture_meta(captured_rdc: Path) -> CaptureMetadata: """Open capture, discover all IDs dynamically, close session.""" session = f"{_DISCOVER_SESSION}_{uuid.uuid4().hex[:8]}" - r = rdc("open", str(captured_rdc), session=session) - assert r.returncode == 0, f"Failed to open capture for discovery: {r.stderr}" - try: + r = rdc("open", str(captured_rdc), session=session) + assert r.returncode == 0, f"Failed to open capture for discovery: {r.stderr}" return _discover_metadata(session) finally: rdc("close", session=session) @@ -71,8 +105,10 @@ def can_replay_prerecorded() -> bool: if not VKCUBE.exists(): return False name = f"e2e_probe_{uuid.uuid4().hex[:8]}" - r = rdc("open", str(VKCUBE), session=name) - rdc("close", session=name) + try: + r = rdc("open", str(VKCUBE), session=name) + finally: + rdc("close", session=name) return r.returncode == 0 @@ -212,10 +248,12 @@ def _discover_metadata(session: str) -> CaptureMetadata: def vkcube_session(captured_rdc: Path) -> Generator[str, None, None]: """Open captured .rdc and yield session name; close on teardown.""" name = f"e2e_vkcube_{uuid.uuid4().hex[:8]}" - r = rdc("open", str(captured_rdc), session=name) - assert r.returncode == 0, f"Failed to open capture: {r.stderr}" - yield name - rdc("close", session=name) + try: + r = rdc("open", str(captured_rdc), session=name) + assert r.returncode == 0, f"Failed to open capture: {r.stderr}" + yield name + finally: + rdc("close", session=name) @pytest.fixture(scope="module") @@ -230,8 +268,10 @@ def dynamic_session() -> Generator[str, None, None]: r = rdc("open", str(DYNAMIC_RENDERING), session=name) if r.returncode != 0: pytest.skip(f"dynamic_rendering.rdc failed to open (GPU mismatch?): {r.stderr}") - yield name - rdc("close", session=name) + try: + yield name + finally: + rdc("close", session=name) @pytest.fixture(scope="module") @@ -246,8 +286,10 @@ def oit_session() -> Generator[str, None, None]: r = rdc("open", str(OIT_DEPTH_PEELING), session=name) if r.returncode != 0: pytest.skip(f"oit_depth_peeling.rdc failed to open (GPU mismatch?): {r.stderr}") - yield name - rdc("close", session=name) + try: + yield name + finally: + rdc("close", session=name) @pytest.fixture(scope="session") diff --git a/tests/e2e/e2e_helpers.py b/tests/e2e/e2e_helpers.py index 584c8205..219b9c69 100644 --- a/tests/e2e/e2e_helpers.py +++ b/tests/e2e/e2e_helpers.py @@ -50,6 +50,16 @@ class CaptureMetadata: VKCUBE_BIN: str | None = os.environ.get("VKCUBE_BIN") or shutil.which("vkcube") +# Extra environment merged into every CLI subprocess. The session-scoped +# isolation fixture in conftest.py populates this with ``RDC_DATA_DIR`` so the +# real CLI never touches the developer's ~/.rdc or leaks live daemons. +SUBPROCESS_ENV: dict[str, str] = {} + + +def _subprocess_env() -> dict[str, str]: + """Return the full environment for a CLI subprocess (os.environ + overrides).""" + return {**os.environ, **SUBPROCESS_ENV} + def self_capture(vkcube_path: str, output: Path, timeout: int = 60) -> Path: """Run ``rdc capture`` against *vkcube_path* and return the .rdc path.""" @@ -58,6 +68,7 @@ def self_capture(vkcube_path: str, output: Path, timeout: int = 60) -> Path: capture_output=True, text=True, timeout=timeout, + env=_subprocess_env(), ) if r.returncode != 0: raise RuntimeError(f"self_capture failed (exit {r.returncode}):\n{r.stderr}") @@ -84,6 +95,7 @@ def rdc( capture_output=True, text=True, timeout=timeout, + env=_subprocess_env(), ) diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 25bcd167..44c60ab1 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -13,6 +13,19 @@ from rdc.daemon_server import DaemonState +@pytest.fixture(autouse=True) +def _isolate_data_dir(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + """Isolate every unit test's rdc data dir to a per-test tmp directory. + + Sets ``RDC_DATA_DIR`` (so any subprocess inherits the override) and patches + ``rdc._platform.data_dir`` (so in-process callers resolve the same path), + guaranteeing tests never read or write the developer's real ``~/.rdc``. + """ + data = tmp_path / ".rdc" + monkeypatch.setenv("RDC_DATA_DIR", str(data)) + monkeypatch.setattr("rdc._platform.data_dir", lambda: data) + + def rpc_request( method: str, params: dict[str, Any] | None = None, diff --git a/tests/unit/test_android_commands.py b/tests/unit/test_android_commands.py index 6850d2be..be425f8b 100644 --- a/tests/unit/test_android_commands.py +++ b/tests/unit/test_android_commands.py @@ -32,11 +32,6 @@ ) -@pytest.fixture(autouse=True) -def _isolate_home(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") - - def _mock_rd_android( monkeypatch: pytest.MonkeyPatch, devices: list[str] | None = None, diff --git a/tests/unit/test_capture_control.py b/tests/unit/test_capture_control.py index d080a431..0c64dbd7 100644 --- a/tests/unit/test_capture_control.py +++ b/tests/unit/test_capture_control.py @@ -3,7 +3,6 @@ from __future__ import annotations import time -from pathlib import Path from typing import Any from unittest.mock import MagicMock @@ -19,11 +18,6 @@ from rdc.target_state import TargetControlState, save_target_state -@pytest.fixture(autouse=True) -def _isolate_home(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") - - def _make_mock_tc( *, connected: bool = True, diff --git a/tests/unit/test_cli_session_flag.py b/tests/unit/test_cli_session_flag.py index 6776cbdb..cb6e9b38 100644 --- a/tests/unit/test_cli_session_flag.py +++ b/tests/unit/test_cli_session_flag.py @@ -77,7 +77,6 @@ def fake_status() -> tuple[bool, object]: def test_two_sessions_isolated(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: """Two named sessions return independent data from their respective files.""" monkeypatch.setenv("HOME", str(tmp_path)) - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.delenv("RDC_SESSION", raising=False) monkeypatch.setattr("rdc.services.session_service._renderdoc_available", lambda: False) mock_proc = MagicMock() diff --git a/tests/unit/test_keep_remote.py b/tests/unit/test_keep_remote.py index 0f68a68a..fee452bb 100644 --- a/tests/unit/test_keep_remote.py +++ b/tests/unit/test_keep_remote.py @@ -2,7 +2,6 @@ from __future__ import annotations -from pathlib import Path from typing import Any from unittest.mock import MagicMock @@ -14,11 +13,6 @@ from rdc.remote_state import RemoteServerState, save_remote_state -@pytest.fixture(autouse=True) -def _isolate_home(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") - - def _save_state() -> None: save_remote_state(RemoteServerState(host="192.168.1.10", port=39920, connected_at=1000.0)) diff --git a/tests/unit/test_platform.py b/tests/unit/test_platform.py index a6a96821..aa09478d 100644 --- a/tests/unit/test_platform.py +++ b/tests/unit/test_platform.py @@ -32,16 +32,77 @@ class TestDataDir: def test_returns_home_dot_rdc(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - """TP-W1-001: Unix data_dir is ~/.rdc.""" + """TP-W1-001: Unix data_dir is ~/.rdc when no override is set.""" + monkeypatch.delenv("RDC_DATA_DIR", raising=False) monkeypatch.setattr("rdc._platform.Path.home", staticmethod(lambda: tmp_path)) assert data_dir() == tmp_path / ".rdc" def test_no_side_effects(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: """TP-W1-002: data_dir does not create the directory.""" + monkeypatch.delenv("RDC_DATA_DIR", raising=False) monkeypatch.setattr("rdc._platform.Path.home", staticmethod(lambda: tmp_path)) result = data_dir() assert not result.exists() + def test_env_override_returns_custom_dir( + self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path + ) -> None: + """RDC_DATA_DIR override wins over the home-based default.""" + custom = tmp_path / "custom-data" + monkeypatch.setenv("RDC_DATA_DIR", str(custom)) + # Even with a different home, the override must take precedence. + monkeypatch.setattr("rdc._platform.Path.home", staticmethod(lambda: tmp_path / "elsewhere")) + assert data_dir() == custom + + def test_env_override_ignored_when_empty( + self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path + ) -> None: + """An empty RDC_DATA_DIR falls back to the home-based default.""" + monkeypatch.setenv("RDC_DATA_DIR", "") + monkeypatch.setattr("rdc._platform.Path.home", staticmethod(lambda: tmp_path)) + assert data_dir() == tmp_path / ".rdc" + + def test_env_override_isolates_session_roundtrip( + self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path + ) -> None: + """With RDC_DATA_DIR set, a session save/load round-trip writes nothing under home. + + Regression: prior to the override seam, session_state always resolved + ``Path.home()/.rdc`` with no way to redirect a subprocess; this asserts + the override fully redirects both the write and the read. + """ + from rdc.session_state import SessionState, load_session, save_session + + fake_home = tmp_path / "home" + fake_home.mkdir() + data = tmp_path / "isolated" + # Restore the genuine data_dir so the env seam (not the conftest patch) + # is what redirects session_state's reads and writes. + monkeypatch.setattr("rdc._platform.data_dir", data_dir) + monkeypatch.setenv("RDC_DATA_DIR", str(data)) + monkeypatch.setattr("rdc._platform.Path.home", staticmethod(lambda: fake_home)) + monkeypatch.delenv("RDC_SESSION", raising=False) + + state = SessionState( + capture="/tmp/x.rdc", + current_eid=7, + opened_at="2026-01-01T00:00:00+00:00", + host="127.0.0.1", + port=4321, + token="tok", + pid=4242, + ) + save_session(state) + + loaded = load_session() + assert loaded is not None + assert loaded.capture == "/tmp/x.rdc" + assert loaded.current_eid == 7 + assert (data / "sessions" / "default.json").exists() + # Nothing must have leaked under the faked home directory. + assert not list(fake_home.rglob("*.json")) + assert not (fake_home / ".rdc").exists() + # ── Group B: terminate_process() ───────────────────────────────────── diff --git a/tests/unit/test_remote_commands.py b/tests/unit/test_remote_commands.py index 17ed6a64..656f2f24 100644 --- a/tests/unit/test_remote_commands.py +++ b/tests/unit/test_remote_commands.py @@ -22,11 +22,6 @@ from rdc.remote_state import RemoteServerState, save_remote_state -@pytest.fixture(autouse=True) -def _isolate_home(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") - - def _mock_rd(monkeypatch: pytest.MonkeyPatch) -> MagicMock: """Provide a mock renderdoc module and patch find_renderdoc.""" rd = MagicMock() diff --git a/tests/unit/test_remote_core.py b/tests/unit/test_remote_core.py index 68329e70..fb29f136 100644 --- a/tests/unit/test_remote_core.py +++ b/tests/unit/test_remote_core.py @@ -119,8 +119,12 @@ def test_parsed_localhost_keys_on_ipv4(self) -> None: host, port = parse_url("localhost:39920") assert (host, port) == ("127.0.0.1", 39920) path = _state_path(host, port) + # The state-file key must use the normalized IPv4, never "localhost". + # (Assert on the relative key, not the absolute path, since the isolated + # data dir lives under a per-test tmp directory whose name may itself + # contain "localhost".) assert path.name == "127.0.0.1_39920.json" - assert "localhost" not in str(path) + assert "localhost" not in path.parent.name class TestWarnIfPublic: diff --git a/tests/unit/test_remote_setup.py b/tests/unit/test_remote_setup.py index 7a30bce3..720cbba1 100644 --- a/tests/unit/test_remote_setup.py +++ b/tests/unit/test_remote_setup.py @@ -4,7 +4,6 @@ import json import socket -from pathlib import Path from typing import Any from unittest.mock import MagicMock @@ -19,11 +18,6 @@ ) -@pytest.fixture(autouse=True) -def _isolate_home(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") - - def _stderr_spy(monkeypatch: pytest.MonkeyPatch) -> list[str]: lines: list[str] = [] orig = click.echo diff --git a/tests/unit/test_remote_state.py b/tests/unit/test_remote_state.py index 92e233a6..87b1cb59 100644 --- a/tests/unit/test_remote_state.py +++ b/tests/unit/test_remote_state.py @@ -18,11 +18,6 @@ _SAMPLE = RemoteServerState(host="192.168.1.10", port=39920, connected_at=1700000000.0) -@pytest.fixture(autouse=True) -def _isolate_home(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") - - def test_save_and_load_round_trip() -> None: save_remote_state(_SAMPLE) loaded = load_remote_state("192.168.1.10", 39920) diff --git a/tests/unit/test_remote_status_disconnect.py b/tests/unit/test_remote_status_disconnect.py index fc0f5a15..443b391d 100644 --- a/tests/unit/test_remote_status_disconnect.py +++ b/tests/unit/test_remote_status_disconnect.py @@ -4,7 +4,6 @@ import json import time -from pathlib import Path from typing import Any import click @@ -19,11 +18,6 @@ from rdc.remote_state import RemoteServerState, save_remote_state -@pytest.fixture(autouse=True) -def _isolate_home(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") - - def _stderr_spy(monkeypatch: pytest.MonkeyPatch) -> list[str]: lines: list[str] = [] orig = click.echo diff --git a/tests/unit/test_session_commands.py b/tests/unit/test_session_commands.py index 2b1ce662..be1e8156 100644 --- a/tests/unit/test_session_commands.py +++ b/tests/unit/test_session_commands.py @@ -59,7 +59,6 @@ def _session_file(home: Path) -> Path: def test_open_status_goto_close_flow(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.delenv("RDC_SESSION", raising=False) monkeypatch.setattr("rdc.services.session_service._renderdoc_available", lambda: False) _mock_daemon(monkeypatch) @@ -89,7 +88,6 @@ def test_open_status_goto_close_flow(monkeypatch: pytest.MonkeyPatch, tmp_path: def test_goto_without_session_fails(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.delenv("RDC_SESSION", raising=False) runner = CliRunner() @@ -98,7 +96,6 @@ def test_goto_without_session_fails(monkeypatch: pytest.MonkeyPatch, tmp_path: P def test_close_without_session_fails(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.delenv("RDC_SESSION", raising=False) runner = CliRunner() @@ -107,7 +104,6 @@ def test_close_without_session_fails(monkeypatch: pytest.MonkeyPatch, tmp_path: def test_goto_rejects_negative_eid(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.delenv("RDC_SESSION", raising=False) monkeypatch.setattr("rdc.services.session_service._renderdoc_available", lambda: False) _mock_daemon(monkeypatch) @@ -120,7 +116,6 @@ def test_goto_rejects_negative_eid(monkeypatch: pytest.MonkeyPatch, tmp_path: Pa def test_status_shows_session_name(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: """rdc status first line is 'session: ' matching active RDC_SESSION.""" - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.setenv("RDC_SESSION", "mytest") monkeypatch.setattr("rdc.services.session_service._renderdoc_available", lambda: False) _mock_daemon(monkeypatch) @@ -137,7 +132,6 @@ def test_status_shows_session_name(monkeypatch: pytest.MonkeyPatch, tmp_path: Pa def test_status_shows_default_session_name(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: """Without --session, status first line is 'session: default'.""" - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.delenv("RDC_SESSION", raising=False) monkeypatch.setattr("rdc.services.session_service._renderdoc_available", lambda: False) _mock_daemon(monkeypatch) @@ -178,7 +172,6 @@ def test_require_session_cleans_stale_pid(monkeypatch: pytest.MonkeyPatch) -> No def test_open_no_replay_mode_warning(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: """B23: open command warns when renderdoc is unavailable.""" - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.delenv("RDC_SESSION", raising=False) monkeypatch.setattr("rdc.services.session_service._renderdoc_available", lambda: False) _mock_daemon(monkeypatch) @@ -198,10 +191,8 @@ def test_open_no_replay_mode_warning(monkeypatch: pytest.MonkeyPatch, tmp_path: def _setup_data_dir(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Path: - """Point data_dir to tmp_path/.rdc and return the path.""" - data = tmp_path / ".rdc" - monkeypatch.setattr("rdc._platform.data_dir", lambda: data) - return data + """Return the isolated data dir path (isolation handled by the autouse fixture).""" + return tmp_path / ".rdc" def _forward(monkeypatch: pytest.MonkeyPatch, port: int | None) -> None: diff --git a/tests/unit/test_session_service.py b/tests/unit/test_session_service.py index b93fd1b0..9f47478c 100644 --- a/tests/unit/test_session_service.py +++ b/tests/unit/test_session_service.py @@ -40,7 +40,6 @@ def test_open_session_cross_name_no_conflict( monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: """Opening session 'b' while 'a' is alive succeeds (conflict is per-name).""" - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.setattr(session_service, "_renderdoc_available", lambda: False) mock_proc = MagicMock() @@ -63,7 +62,6 @@ def test_open_session_same_name_alive_fails( monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: """Opening the same session name twice (alive pid) returns error.""" - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.setenv("RDC_SESSION", "alpha") monkeypatch.setattr(session_service, "_renderdoc_available", lambda: False) @@ -126,7 +124,6 @@ def _refuse(*args: object, **kwargs: object) -> None: def test_open_session_reports_stderr_on_failure( monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.setattr(session_service, "load_session", lambda: None) monkeypatch.setattr(session_service, "_renderdoc_available", lambda: False) @@ -155,7 +152,6 @@ def _start(*_a: object, **_kw: object) -> MagicMock: def test_open_session_failure_with_empty_stderr( monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.setattr(session_service, "load_session", lambda: None) monkeypatch.setattr(session_service, "_renderdoc_available", lambda: False) @@ -236,7 +232,6 @@ def test_open_session_retries_on_port_conflict( monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: """B22: open_session retries up to 3 times on daemon start failure.""" - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.delenv("RDC_SESSION", raising=False) monkeypatch.setattr(session_service, "load_session", lambda: None) monkeypatch.setattr(session_service, "_renderdoc_available", lambda: False) @@ -264,7 +259,6 @@ def fake_ping(*a: object, **kw: object) -> tuple[bool, str]: def test_open_session_all_retries_fail(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: """B22: open_session returns error after 3 failed attempts.""" - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.delenv("RDC_SESSION", raising=False) monkeypatch.setattr(session_service, "load_session", lambda: None) monkeypatch.setattr(session_service, "_renderdoc_available", lambda: False) @@ -287,7 +281,6 @@ def test_close_session_fallback_kill_on_shutdown_error( monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: """B25: close_session sends SIGTERM as fallback when shutdown RPC fails.""" - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.delenv("RDC_SESSION", raising=False) monkeypatch.setattr(session_service, "_renderdoc_available", lambda: False) @@ -365,7 +358,6 @@ def test_kill_daemon_on_port_noop_when_no_pid( def test_close_session_tree_kill_on_hang(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: """Regression: close_session calls terminate_process_tree when daemon hangs after shutdown.""" - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.delenv("RDC_SESSION", raising=False) monkeypatch.setattr(session_service, "_renderdoc_available", lambda: False) @@ -418,7 +410,6 @@ def test_open_session_does_not_hang_on_flooded_stderr( timeout while the pipe stays full. This asserts the call returns well inside the child's 30s sleep and that the flooded stderr tail is still surfaced. """ - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.setenv("RDC_SESSION", "flood") monkeypatch.setattr(session_service, "_renderdoc_available", lambda: False) diff --git a/tests/unit/test_session_state.py b/tests/unit/test_session_state.py index 1bed20e3..fb922db9 100644 --- a/tests/unit/test_session_state.py +++ b/tests/unit/test_session_state.py @@ -97,25 +97,21 @@ def _raise(*_a: object, **_kw: object) -> None: def test_session_path_reads_env_var(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.setenv("RDC_SESSION", "foo") assert session_path() == tmp_path / ".rdc" / "sessions" / "foo.json" def test_session_path_default_no_env(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.delenv("RDC_SESSION", raising=False) assert session_path() == tmp_path / ".rdc" / "sessions" / "default.json" def test_session_path_default_empty_env(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.setenv("RDC_SESSION", "") assert session_path() == tmp_path / ".rdc" / "sessions" / "default.json" def test_session_path_rejects_traversal(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.setenv("RDC_SESSION", "../../etc/evil") assert session_path() == tmp_path / ".rdc" / "sessions" / "default.json" diff --git a/tests/unit/test_shader_preload.py b/tests/unit/test_shader_preload.py index 0d996a87..3d304654 100644 --- a/tests/unit/test_shader_preload.py +++ b/tests/unit/test_shader_preload.py @@ -261,7 +261,6 @@ def test_idempotent(self, state: DaemonState) -> None: class TestOpenPreloadFlag: def test_preload_calls_rpc(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.delenv("RDC_SESSION", raising=False) monkeypatch.setattr("rdc.services.session_service._renderdoc_available", lambda: False) mock_proc = MagicMock() @@ -299,7 +298,6 @@ def _capture_send(_h: str, _p: int, payload: dict[str, Any], **_kw: Any) -> dict def test_no_preload_does_not_call_rpc( self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.delenv("RDC_SESSION", raising=False) monkeypatch.setattr("rdc.services.session_service._renderdoc_available", lambda: False) mock_proc = MagicMock() diff --git a/tests/unit/test_split_core.py b/tests/unit/test_split_core.py index d726db49..2adca2ef 100644 --- a/tests/unit/test_split_core.py +++ b/tests/unit/test_split_core.py @@ -38,7 +38,6 @@ def _make_session( def _setup_no_replay(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") monkeypatch.delenv("RDC_SESSION", raising=False) monkeypatch.setattr("rdc.services.session_service._renderdoc_available", lambda: False) diff --git a/tests/unit/test_target_state.py b/tests/unit/test_target_state.py index 2d5d1d21..b4da6052 100644 --- a/tests/unit/test_target_state.py +++ b/tests/unit/test_target_state.py @@ -4,8 +4,6 @@ from pathlib import Path -import pytest - from rdc.target_state import ( TargetControlState, delete_target_state, @@ -23,11 +21,6 @@ ) -@pytest.fixture(autouse=True) -def _isolate_home(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: - monkeypatch.setattr("rdc._platform.data_dir", lambda: tmp_path / ".rdc") - - def test_save_load() -> None: save_target_state(_SAMPLE) loaded = load_target_state(12345)