refactor: move bot_bottle_root/host_db_path to paths; kill the monkeypatch (#352)
Create bot_bottle/paths.py as the canonical home for the app-root path helpers (bot_bottle_root, host_db_path, HOST_DB_FILENAME) — foundational, not supervise- or db-specific. `bot_bottle_root()` now honours a BOT_BOTTLE_ROOT env override. Repoint every consumer (supervise, supervise_types, db_store, queue_store, audit_store, store_manager, bottle_state, cli/supervise, docker/cleanup, orchestrator/registry) at paths; remove the definitions (and supervise's duplicate host_db_path) and the now-dead `import sys`. Add paths.py to the sidecar bundle (Dockerfile.sidecars) for the flat-import copies. Tests: replace ~12 files' monkeypatching of supervise.bot_bottle_root (and the flat/pkg/supervise_types triple-patch dance) with a single `use_bottle_root()` helper that sets BOT_BOTTLE_ROOT — every module and flat/package copy reads the same env var, so one override covers them all. Net -97 lines. Behaviour-preserving: full unit suite unchanged (only the pre-existing /bin/sleep sidecar-init errors remain). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
This commit is contained in:
+21
-2
@@ -10,8 +10,12 @@ and unisolated tests otherwise pollute the developer's home dir.
|
||||
|
||||
Individual tests that need their own ``HOME`` still override
|
||||
``os.environ['HOME']`` and restore it; they now restore to this isolated
|
||||
dir rather than the real one, so isolation holds either way. Tests that
|
||||
patch ``supervise.bot_bottle_root`` directly are unaffected.
|
||||
dir rather than the real one, so isolation holds either way.
|
||||
|
||||
Tests that need their own app-data root call ``use_bottle_root`` (below),
|
||||
which sets ``BOT_BOTTLE_ROOT`` — the supported override read by
|
||||
``paths.bot_bottle_root`` — instead of monkey-patching. One env setting
|
||||
covers every module and every flat/package copy of the helper.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -20,6 +24,9 @@ import atexit
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
_real_home = os.environ.get("HOME")
|
||||
_tmp_home = tempfile.mkdtemp(prefix="bot-bottle-unit-home.")
|
||||
@@ -35,3 +42,15 @@ def _restore_home() -> None:
|
||||
|
||||
|
||||
atexit.register(_restore_home)
|
||||
|
||||
|
||||
def use_bottle_root(root: Path) -> Callable[[], None]:
|
||||
"""Redirect ``paths.bot_bottle_root()`` to ``root`` by setting
|
||||
``BOT_BOTTLE_ROOT`` — the supported override. Returns a callable that
|
||||
undoes it (store it as the test's restore hook, or pass to addCleanup).
|
||||
|
||||
Replaces monkey-patching ``supervise.bot_bottle_root``; one env var
|
||||
covers every module and the flat/package copies alike (they all read it)."""
|
||||
patcher = mock.patch.dict(os.environ, {"BOT_BOTTLE_ROOT": str(root)})
|
||||
patcher.start()
|
||||
return patcher.stop
|
||||
|
||||
@@ -7,7 +7,8 @@ import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from bot_bottle import supervise, bottle_state
|
||||
from bot_bottle import bottle_state
|
||||
from tests.unit import use_bottle_root
|
||||
from bot_bottle.backend import ActiveAgent
|
||||
from bot_bottle.backend.freeze import get_freezer
|
||||
from bot_bottle.backend.docker.freezer import DockerFreezer
|
||||
@@ -18,13 +19,7 @@ from bot_bottle.backend.firecracker.freezer import FirecrackerFreezer
|
||||
class _FakeHomeMixin:
|
||||
def _setup_fake_home(self):
|
||||
self._tmp = tempfile.TemporaryDirectory(prefix="freezer-test.")
|
||||
original = supervise.bot_bottle_root
|
||||
|
||||
def fake_root() -> Path:
|
||||
return Path(self._tmp.name) / ".bot-bottle"
|
||||
|
||||
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
self._restore = lambda: setattr(supervise, "bot_bottle_root", original)
|
||||
self._restore = use_bottle_root(Path(self._tmp.name) / ".bot-bottle")
|
||||
|
||||
def _teardown_fake_home(self):
|
||||
self._restore()
|
||||
|
||||
@@ -13,7 +13,7 @@ from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from bot_bottle import bottle_state
|
||||
from bot_bottle import supervise
|
||||
from tests.unit import use_bottle_root
|
||||
from bot_bottle.backend import BottleSpec
|
||||
from bot_bottle.backend.docker import DockerBottleBackend
|
||||
from bot_bottle.backend.resolve_common import mint_slug
|
||||
@@ -55,11 +55,10 @@ class _FakeStateMixin:
|
||||
def setUp(self) -> None:
|
||||
self.tmp = tempfile.TemporaryDirectory(prefix="backend-prepare.")
|
||||
self.root = Path(self.tmp.name) / ".bot-bottle"
|
||||
self.original_root = supervise.bot_bottle_root
|
||||
supervise.bot_bottle_root = lambda: self.root # type: ignore[assignment]
|
||||
self._restore = use_bottle_root(self.root)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
supervise.bot_bottle_root = self.original_root # type: ignore[assignment]
|
||||
self._restore()
|
||||
self.tmp.cleanup()
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from pathlib import Path
|
||||
from unittest.mock import MagicMock, call, patch
|
||||
|
||||
from bot_bottle import bottle_state
|
||||
from bot_bottle import supervise
|
||||
from tests.unit import use_bottle_root
|
||||
from bot_bottle.backend import Bottle, BottleSpec, ExecResult
|
||||
from bot_bottle.backend.docker import DockerBottleBackend
|
||||
from bot_bottle.backend.firecracker import FirecrackerBottleBackend
|
||||
@@ -55,11 +55,10 @@ class _FakeStateMixin:
|
||||
self.tmp_dir = tempfile.TemporaryDirectory(prefix="backend-workspace.")
|
||||
self.tmp = Path(self.tmp_dir.name)
|
||||
self.root = self.tmp / ".bot-bottle"
|
||||
self.original_root = supervise.bot_bottle_root
|
||||
supervise.bot_bottle_root = lambda: self.root # type: ignore[assignment]
|
||||
self._restore = use_bottle_root(self.root)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
supervise.bot_bottle_root = self.original_root # type: ignore[assignment]
|
||||
self._restore()
|
||||
self.tmp_dir.cleanup()
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from bot_bottle import supervise
|
||||
from tests.unit import use_bottle_root
|
||||
from bot_bottle import bottle_state
|
||||
from bot_bottle.bottle_state import (
|
||||
BottleMetadata,
|
||||
@@ -18,13 +18,7 @@ from bot_bottle.bottle_state import (
|
||||
class _FakeHomeMixin:
|
||||
def _setup_fake_home(self):
|
||||
self._tmp = tempfile.TemporaryDirectory(prefix="bottle-state-test.")
|
||||
original = supervise.bot_bottle_root
|
||||
|
||||
def fake_root() -> Path:
|
||||
return Path(self._tmp.name) / ".bot-bottle"
|
||||
|
||||
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
self._restore = lambda: setattr(supervise, "bot_bottle_root", original)
|
||||
self._restore = use_bottle_root(Path(self._tmp.name) / ".bot-bottle")
|
||||
|
||||
def _teardown_fake_home(self):
|
||||
self._restore()
|
||||
|
||||
@@ -8,7 +8,7 @@ from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from bot_bottle.cli.commit import cmd_commit
|
||||
from bot_bottle import supervise
|
||||
from tests.unit import use_bottle_root
|
||||
from bot_bottle import bottle_state
|
||||
from bot_bottle.backend.freeze import CommitCancelled
|
||||
|
||||
@@ -16,13 +16,7 @@ from bot_bottle.backend.freeze import CommitCancelled
|
||||
class _FakeHomeMixin:
|
||||
def _setup_fake_home(self):
|
||||
self._tmp = tempfile.TemporaryDirectory(prefix="cli-commit-test.")
|
||||
original = supervise.bot_bottle_root
|
||||
|
||||
def fake_root() -> Path:
|
||||
return Path(self._tmp.name) / ".bot-bottle"
|
||||
|
||||
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
self._restore = lambda: setattr(supervise, "bot_bottle_root", original)
|
||||
self._restore = use_bottle_root(Path(self._tmp.name) / ".bot-bottle")
|
||||
|
||||
def _teardown_fake_home(self):
|
||||
self._restore()
|
||||
|
||||
@@ -8,7 +8,7 @@ import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from bot_bottle import supervise
|
||||
from tests.unit import use_bottle_root
|
||||
from bot_bottle import bottle_state
|
||||
from bot_bottle.cli import start as start_mod
|
||||
|
||||
@@ -16,15 +16,10 @@ from bot_bottle.cli import start as start_mod
|
||||
class _FakeHomeMixin:
|
||||
def _setup_fake_home(self):
|
||||
self._tmp = tempfile.TemporaryDirectory(prefix="cli-start-settle.")
|
||||
self._original = supervise.bot_bottle_root
|
||||
|
||||
def fake_root() -> Path:
|
||||
return Path(self._tmp.name) / ".bot-bottle"
|
||||
|
||||
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
self._restore = use_bottle_root(Path(self._tmp.name) / ".bot-bottle")
|
||||
|
||||
def _teardown_fake_home(self):
|
||||
supervise.bot_bottle_root = self._original # type: ignore[assignment]
|
||||
self._restore()
|
||||
self._tmp.cleanup()
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from bot_bottle import supervise
|
||||
from tests.unit import use_bottle_root
|
||||
from bot_bottle import bottle_state
|
||||
from bot_bottle.backend.docker.cleanup import _list_orphan_state_dirs
|
||||
|
||||
@@ -23,13 +23,7 @@ from bot_bottle.backend.docker.cleanup import _list_orphan_state_dirs
|
||||
class _FakeHomeMixin:
|
||||
def _setup_fake_home(self) -> None:
|
||||
self._tmp = tempfile.TemporaryDirectory(prefix="docker-cleanup-test.")
|
||||
original = supervise.bot_bottle_root
|
||||
|
||||
def fake_root() -> Path:
|
||||
return Path(self._tmp.name) / ".bot-bottle"
|
||||
|
||||
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
self._restore = lambda: setattr(supervise, "bot_bottle_root", original)
|
||||
self._restore = use_bottle_root(Path(self._tmp.name) / ".bot-bottle")
|
||||
|
||||
def _teardown_fake_home(self) -> None:
|
||||
self._restore()
|
||||
|
||||
@@ -23,7 +23,7 @@ import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from bot_bottle import supervise
|
||||
from tests.unit import use_bottle_root
|
||||
from bot_bottle import bottle_state
|
||||
from bot_bottle.backend.docker import enumerate as _enumerate
|
||||
|
||||
@@ -75,13 +75,7 @@ class TestParseServicesByProject(unittest.TestCase):
|
||||
class _FakeHomeMixin:
|
||||
def _setup_fake_home(self) -> None:
|
||||
self._tmp = tempfile.TemporaryDirectory(prefix="enum-active.")
|
||||
original = supervise.bot_bottle_root
|
||||
|
||||
def fake_root() -> Path:
|
||||
return Path(self._tmp.name) / ".bot-bottle"
|
||||
|
||||
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
self._restore_home = lambda: setattr(supervise, "bot_bottle_root", original)
|
||||
self._restore_home = use_bottle_root(Path(self._tmp.name) / ".bot-bottle")
|
||||
|
||||
def _teardown_fake_home(self) -> None:
|
||||
self._restore_home()
|
||||
|
||||
@@ -8,7 +8,7 @@ from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
from bot_bottle import supervise
|
||||
from tests.unit import use_bottle_root
|
||||
from bot_bottle.backend.egress_apply import EgressApplyError
|
||||
from bot_bottle.backend.docker.egress_apply import applicator
|
||||
|
||||
@@ -67,14 +67,8 @@ class TestValidateRoutesContent(unittest.TestCase):
|
||||
class TestApplyRoutesChange(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self._tmp = tempfile.TemporaryDirectory(prefix="egress-apply-test.")
|
||||
original = supervise.bot_bottle_root
|
||||
|
||||
def fake_root() -> Path:
|
||||
return Path(self._tmp.name) / ".bot-bottle"
|
||||
|
||||
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
self.addCleanup(lambda: setattr(supervise, "bot_bottle_root", original))
|
||||
self.addCleanup(self._tmp.cleanup)
|
||||
self.addCleanup(use_bottle_root(Path(self._tmp.name) / ".bot-bottle"))
|
||||
|
||||
def test_writes_live_routes_and_signals_reload(self):
|
||||
calls: list[list[str]] = []
|
||||
|
||||
@@ -8,7 +8,7 @@ from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from bot_bottle import supervise
|
||||
from bot_bottle import supervise_types as sv_types
|
||||
from tests.unit import use_bottle_root
|
||||
from bot_bottle.audit_store import AuditStore
|
||||
from bot_bottle.queue_store import QueueStore
|
||||
from bot_bottle.supervise import (
|
||||
@@ -123,20 +123,7 @@ class TestQueueIO(unittest.TestCase):
|
||||
self._tmp.cleanup()
|
||||
|
||||
def _patch_home(self, fake_home: Path):
|
||||
original_sv = supervise.bot_bottle_root
|
||||
original_svt = sv_types.bot_bottle_root
|
||||
|
||||
def fake_root() -> Path:
|
||||
return fake_home / ".bot-bottle"
|
||||
|
||||
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
sv_types.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
|
||||
def restore() -> None:
|
||||
supervise.bot_bottle_root = original_sv # type: ignore[assignment]
|
||||
sv_types.bot_bottle_root = original_svt # type: ignore[assignment]
|
||||
|
||||
return restore
|
||||
return use_bottle_root(fake_home / ".bot-bottle")
|
||||
|
||||
def test_write_and_read_proposal(self):
|
||||
p = _proposal()
|
||||
@@ -240,20 +227,7 @@ class TestAuditLog(unittest.TestCase):
|
||||
self._tmp.cleanup()
|
||||
|
||||
def _patch_home(self, fake_home: Path):
|
||||
original_sv = supervise.bot_bottle_root
|
||||
original_svt = sv_types.bot_bottle_root
|
||||
|
||||
def fake_root() -> Path:
|
||||
return fake_home / ".bot-bottle"
|
||||
|
||||
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
sv_types.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
|
||||
def restore() -> None:
|
||||
supervise.bot_bottle_root = original_sv # type: ignore[assignment]
|
||||
sv_types.bot_bottle_root = original_svt # type: ignore[assignment]
|
||||
|
||||
return restore
|
||||
return use_bottle_root(fake_home / ".bot-bottle")
|
||||
|
||||
def test_write_then_read_single_entry(self):
|
||||
e = AuditEntry(
|
||||
@@ -400,20 +374,7 @@ class TestSupervisePrepare(unittest.TestCase):
|
||||
self._tmp.cleanup()
|
||||
|
||||
def _patch_home(self, fake_home: Path):
|
||||
original_sv = supervise.bot_bottle_root
|
||||
original_svt = sv_types.bot_bottle_root
|
||||
|
||||
def fake_root() -> Path:
|
||||
return fake_home / ".bot-bottle"
|
||||
|
||||
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
sv_types.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
|
||||
def restore() -> None:
|
||||
supervise.bot_bottle_root = original_sv # type: ignore[assignment]
|
||||
sv_types.bot_bottle_root = original_svt # type: ignore[assignment]
|
||||
|
||||
return restore
|
||||
return use_bottle_root(fake_home / ".bot-bottle")
|
||||
|
||||
def test_prepare_creates_queue(self):
|
||||
plan = _StubSupervise().prepare("dev", self.stage_dir)
|
||||
|
||||
@@ -12,7 +12,7 @@ from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from bot_bottle import supervise
|
||||
from bot_bottle import supervise_types as sv_types
|
||||
from tests.unit import use_bottle_root
|
||||
from bot_bottle.audit_store import AuditStore
|
||||
from bot_bottle.cli import supervise as supervise_cli
|
||||
from bot_bottle.queue_store import QueueStore
|
||||
@@ -55,20 +55,7 @@ class _FakeHomeMixin:
|
||||
|
||||
def _setup_fake_home(self):
|
||||
self._tmp = tempfile.TemporaryDirectory(prefix="supervise-test.")
|
||||
original_sv = supervise.bot_bottle_root
|
||||
original_svt = sv_types.bot_bottle_root
|
||||
|
||||
def fake_root() -> Path:
|
||||
return Path(self._tmp.name) / ".bot-bottle"
|
||||
|
||||
supervise.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
sv_types.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
|
||||
def restore() -> None:
|
||||
supervise.bot_bottle_root = original_sv # type: ignore[assignment]
|
||||
sv_types.bot_bottle_root = original_svt # type: ignore[assignment]
|
||||
|
||||
self._restore_home = restore
|
||||
self._restore_home = use_bottle_root(Path(self._tmp.name) / ".bot-bottle")
|
||||
QueueStore("").migrate()
|
||||
AuditStore().migrate()
|
||||
|
||||
|
||||
@@ -11,12 +11,13 @@ from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
from bot_bottle import supervise
|
||||
from tests.unit import use_bottle_root
|
||||
from bot_bottle.cli import supervise as supervise_cli
|
||||
from bot_bottle.log import Die, die
|
||||
|
||||
@@ -45,12 +46,11 @@ class _FakeHomeMixin:
|
||||
|
||||
def _setup_fake_home(self):
|
||||
self._tmp = tempfile.TemporaryDirectory(prefix="supervise-crash-test.")
|
||||
self._orig_root = supervise.bot_bottle_root
|
||||
self._root = Path(self._tmp.name) / ".bot-bottle"
|
||||
supervise.bot_bottle_root = lambda: self._root # type: ignore[assignment]
|
||||
self._restore_root = use_bottle_root(self._root)
|
||||
|
||||
def _teardown_fake_home(self):
|
||||
supervise.bot_bottle_root = self._orig_root # type: ignore[assignment]
|
||||
self._restore_root()
|
||||
self._tmp.cleanup()
|
||||
|
||||
|
||||
@@ -127,11 +127,11 @@ class TestWriteCrashLog(_FakeHomeMixin, unittest.TestCase):
|
||||
# OSError and the helper must fall back to a tempfile.
|
||||
bad = Path(self._tmp.name) / "not-a-dir"
|
||||
bad.write_text("x")
|
||||
supervise.bot_bottle_root = lambda: bad # type: ignore[assignment]
|
||||
try:
|
||||
raise RuntimeError("explode2")
|
||||
except RuntimeError as e:
|
||||
path = supervise_cli._write_crash_log(e)
|
||||
with mock.patch.dict(os.environ, {"BOT_BOTTLE_ROOT": str(bad)}):
|
||||
try:
|
||||
raise RuntimeError("explode2")
|
||||
except RuntimeError as e:
|
||||
path = supervise_cli._write_crash_log(e)
|
||||
self.assertTrue(path.exists())
|
||||
self.assertIn("explode2", path.read_text())
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from tests.unit import use_bottle_root
|
||||
|
||||
|
||||
# The server module loads `supervise` via same-directory import inside
|
||||
# the container (Dockerfile.supervise WORKDIRs into /app). For tests
|
||||
@@ -19,10 +21,8 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "bot_bott
|
||||
import supervise as _sv # noqa: E402 # type: ignore
|
||||
import queue_store as _qs # noqa: E402 # type: ignore
|
||||
import audit_store as _as # noqa: E402 # type: ignore
|
||||
import supervise_types as svt_flat # noqa: E402 # type: ignore
|
||||
|
||||
from bot_bottle import supervise_server # noqa: E402
|
||||
from bot_bottle import supervise_types as svt_pkg # noqa: E402
|
||||
from bot_bottle.supervise_server import (
|
||||
ERR_INTERNAL,
|
||||
ERR_INVALID_PARAMS,
|
||||
@@ -279,23 +279,7 @@ class TestHandleToolsCall(unittest.TestCase):
|
||||
self._tmp.cleanup()
|
||||
|
||||
def _patch_home(self, fake_home: Path):
|
||||
original_sv = _sv.bot_bottle_root
|
||||
original_flat = svt_flat.bot_bottle_root
|
||||
original_pkg = svt_pkg.bot_bottle_root
|
||||
|
||||
def fake_root() -> Path:
|
||||
return fake_home / ".bot-bottle"
|
||||
|
||||
_sv.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
svt_flat.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
svt_pkg.bot_bottle_root = fake_root # type: ignore[assignment]
|
||||
|
||||
def restore() -> None:
|
||||
_sv.bot_bottle_root = original_sv # type: ignore[assignment]
|
||||
svt_flat.bot_bottle_root = original_flat # type: ignore[assignment]
|
||||
svt_pkg.bot_bottle_root = original_pkg # type: ignore[assignment]
|
||||
|
||||
return restore
|
||||
return use_bottle_root(fake_home / ".bot-bottle")
|
||||
|
||||
def _respond_when_proposal_appears(self, status: str, notes: str = "") -> threading.Thread:
|
||||
"""Background thread: poll the queue for a fresh proposal, write a
|
||||
|
||||
Reference in New Issue
Block a user