cf56d07c9e
The recent refactor partially removed workspace planning and capability-apply logic. This commit finishes the cleanup so the test suite imports cleanly: - Comment out workspace_plan field/property on BottlePlan and the provision_workspace dispatch. - Comment out workspace usages in docker.util (build_image_with_cwd), smolmachines.provision.workspace, agent_provider.provision_git, smolmachines.backend. - Comment out capability_apply imports in cli.start and cli.supervise; add a local CapabilityApplyError placeholder so the supervise CLI module still imports. - Break the bottle_state → backend.docker → backend circular import by lazy-loading docker_mod inside bottle_identity, and by moving the resolve_common import inside BottleBackend.prepare. - Delete tests for workspace and capability_apply (unit + integration). - Update test fixtures to drop removed kwargs (container_name_pinned, derived_image, env_file, workspace_plan, agent_image_ref) from DockerBottlePlan / SmolmachinesBottlePlan constructors. - Delete the obsolete test_smolmachines_prepare.py (tested the old resolve_plan signature; the shared prepare flow now lives in BottleBackend.prepare). - Adjust test_supervise.py for the new Supervise.prepare signature (dockerfile_content arg removed). 925 → 897 tests, all passing.
133 lines
4.3 KiB
Python
133 lines
4.3 KiB
Python
"""Unit: Docker launch teardown warning on ExitStack failure (issue #156).
|
|
|
|
When a callback registered in the ExitStack raises during teardown,
|
|
the teardown function must emit a WARNING-level message that includes
|
|
the container name and operation type, rather than silently discarding
|
|
the exception.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import io
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
from bot_bottle.agent_provider import AgentProvisionPlan
|
|
from bot_bottle.backend import BottleSpec
|
|
from bot_bottle.backend.docker import launch as launch_mod
|
|
from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
|
|
from bot_bottle.egress import EgressPlan
|
|
from bot_bottle.git_gate import GitGatePlan
|
|
from bot_bottle.manifest import Manifest
|
|
# from bot_bottle.workspace import workspace_plan
|
|
|
|
|
|
def _manifest() -> Manifest:
|
|
return Manifest.from_json_obj({
|
|
"bottles": {"dev": {}},
|
|
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
|
})
|
|
|
|
|
|
def _plan(tmp: str) -> DockerBottlePlan:
|
|
stage = Path(tmp)
|
|
manifest = _manifest()
|
|
spec = BottleSpec(
|
|
manifest=manifest,
|
|
agent_name="demo",
|
|
copy_cwd=False,
|
|
user_cwd=tmp,
|
|
identity="test-teardown-00001",
|
|
)
|
|
return DockerBottlePlan(
|
|
spec=spec,
|
|
stage_dir=stage,
|
|
git_gate_plan=GitGatePlan(
|
|
slug="test-teardown-00001",
|
|
entrypoint_script=stage / "entrypoint.sh",
|
|
hook_script=stage / "hook.sh",
|
|
access_hook_script=stage / "access-hook.sh",
|
|
upstreams=(),
|
|
),
|
|
egress_plan=EgressPlan(
|
|
slug="test-teardown-00001",
|
|
routes_path=stage / "egress.yaml",
|
|
routes=(),
|
|
token_env_map={},
|
|
),
|
|
supervise_plan=None,
|
|
agent_provision=AgentProvisionPlan(
|
|
template="claude",
|
|
command="claude",
|
|
prompt_mode="append_file",
|
|
image="",
|
|
dockerfile="",
|
|
guest_home="/home/node",
|
|
guest_env={},
|
|
),
|
|
slug="test-teardown-00001",
|
|
container_name="bot-bottle-test-teardown-abc",
|
|
image="bot-bottle-claude:latest",
|
|
dockerfile_path="",
|
|
forwarded_env={},
|
|
prompt_file=stage / "prompt.txt",
|
|
use_runsc=False,
|
|
)
|
|
|
|
|
|
class TestTeardownWarning(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self._tmp = tempfile.mkdtemp(prefix="docker-launch-teardown-test.")
|
|
|
|
def tearDown(self) -> None:
|
|
import shutil
|
|
shutil.rmtree(self._tmp, ignore_errors=True)
|
|
|
|
def test_teardown_failure_emits_warning_with_container_and_operation(self):
|
|
plan = _plan(self._tmp)
|
|
buf = io.StringIO()
|
|
|
|
with mock.patch.object(launch_mod.docker_mod, "build_image"), \
|
|
mock.patch.object(
|
|
launch_mod, "egress_tls_init",
|
|
return_value=(Path("/egress_ca"), Path("/egress_cert")),
|
|
), \
|
|
mock.patch.object(
|
|
launch_mod.network_mod, "network_name_for_slug",
|
|
return_value="bb-internal-test",
|
|
), \
|
|
mock.patch.object(
|
|
launch_mod.network_mod, "network_egress_name_for_slug",
|
|
return_value="bb-egress-test",
|
|
), \
|
|
mock.patch.object(
|
|
launch_mod, "bottle_plan_to_compose",
|
|
return_value={"services": {"agent": {}}},
|
|
), \
|
|
mock.patch.object(
|
|
launch_mod, "write_compose_file",
|
|
return_value=Path("/tmp/compose.yml"),
|
|
), \
|
|
mock.patch.object(launch_mod, "compose_up"), \
|
|
mock.patch.object(launch_mod, "compose_dump_logs"), \
|
|
mock.patch.object(
|
|
launch_mod, "compose_down",
|
|
side_effect=RuntimeError("network remove failed"),
|
|
), \
|
|
contextlib.redirect_stderr(buf):
|
|
provision = mock.Mock(return_value=None)
|
|
with launch_mod.launch(plan, provision=provision):
|
|
pass
|
|
|
|
output = buf.getvalue()
|
|
self.assertIn("bot-bottle: warning:", output)
|
|
self.assertIn("bot-bottle-test-teardown-abc", output)
|
|
self.assertIn("compose-down", output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|