From 626f07efa6f64af5234a7518daa94791a9ec9529 Mon Sep 17 00:00:00 2001 From: codex Date: Sun, 19 Jul 2026 02:34:53 +0000 Subject: [PATCH] fix(tests): run sandbox integration on firecracker --- tests/_docker.py | 13 +++++++++ tests/integration/test_sandbox_escape.py | 12 ++++---- tests/unit/test_docker_test_helpers.py | 35 ++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 tests/unit/test_docker_test_helpers.py diff --git a/tests/_docker.py b/tests/_docker.py index 4de74fb..4cb7a25 100644 --- a/tests/_docker.py +++ b/tests/_docker.py @@ -30,3 +30,16 @@ def docker_available() -> bool: def skip_unless_docker(reason: str = "docker unreachable"): return unittest.skipUnless(docker_available(), reason) + + +def skip_unless_docker_or_firecracker( + reason: str = "neither Docker nor Firecracker selected", +): + """Skip a backend-agnostic test unless one supported backend can run. + + Firecracker does not require the host Docker daemon. The KVM coverage job + deliberately sets ``SKIP_DOCKER_TESTS`` to exclude Docker-only integration + classes while still exercising this path. + """ + firecracker_selected = os.environ.get("BOT_BOTTLE_BACKEND") == "firecracker" + return unittest.skipUnless(firecracker_selected or docker_available(), reason) diff --git a/tests/integration/test_sandbox_escape.py b/tests/integration/test_sandbox_escape.py index 2b2ed05..29b7608 100644 --- a/tests/integration/test_sandbox_escape.py +++ b/tests/integration/test_sandbox_escape.py @@ -31,7 +31,7 @@ from pathlib import Path from bot_bottle.backend import BottleSpec, get_bottle_backend from bot_bottle.bottle_state import cleanup_state from bot_bottle.manifest import ManifestIndex -from tests._docker import skip_unless_docker +from tests._docker import skip_unless_docker_or_firecracker # Secrets planted in the bottle env as literals (agents substitute via @@ -67,7 +67,7 @@ _DUMMY_HOST_KEY = ( ) -@skip_unless_docker() +@skip_unless_docker_or_firecracker() @unittest.skipIf( os.environ.get("GITEA_ACTIONS") == "true" and os.environ.get("BOT_BOTTLE_BACKEND") != "firecracker", @@ -91,11 +91,9 @@ class TestSandboxEscape(unittest.TestCase): @classmethod def setUpClass(cls) -> None: - # Docker is always required (the agent + companion containers run under it, - # and VM backends still use it for the gateway); the - # class-level @skip_unless_docker already covers that. Pin - # Docker when BOT_BOTTLE_BACKEND is unset to preserve the - # Docker-backed CI path. + # Pin Docker when BOT_BOTTLE_BACKEND is unset to preserve the + # Docker-backed CI path. Firecracker uses its persistent infra VM for + # the shared gateway and therefore does not require host Docker. cls._backend_name = os.environ.get("BOT_BOTTLE_BACKEND", "docker") # Throwaway static key for the git-gate fixture. It need not diff --git a/tests/unit/test_docker_test_helpers.py b/tests/unit/test_docker_test_helpers.py new file mode 100644 index 0000000..c7f9a33 --- /dev/null +++ b/tests/unit/test_docker_test_helpers.py @@ -0,0 +1,35 @@ +"""Tests for integration-test backend selection helpers.""" + +from __future__ import annotations + +import os +import unittest +from unittest.mock import patch + +from tests._docker import skip_unless_docker_or_firecracker + + +class TestSkipUnlessDockerOrFirecracker(unittest.TestCase): + def test_firecracker_runs_when_docker_tests_are_disabled(self): + with patch.dict( + os.environ, + {"BOT_BOTTLE_BACKEND": "firecracker", "SKIP_DOCKER_TESTS": "1"}, + clear=True, + ): + decorated = skip_unless_docker_or_firecracker()(type("Case", (), {})) + + self.assertFalse(getattr(decorated, "__unittest_skip__", False)) + + def test_non_firecracker_still_skips_when_docker_tests_are_disabled(self): + with patch.dict( + os.environ, + {"BOT_BOTTLE_BACKEND": "docker", "SKIP_DOCKER_TESTS": "1"}, + clear=True, + ): + decorated = skip_unless_docker_or_firecracker()(type("Case", (), {})) + + self.assertTrue(decorated.__unittest_skip__) + + +if __name__ == "__main__": + unittest.main()