From 18b34298ccb7fcc0ca6fc7ce1ffd9c0d7211cdce Mon Sep 17 00:00:00 2001 From: theater <1347507191@qq.com> Date: Sun, 6 Sep 2026 10:39:18 +0800 Subject: [PATCH] fix(tests): preload sandbox leaf modules before mocking their parent package (#5215) TestBashExecutionHarvest's shell-persistence tests report shell_persistent=None whenever deerflow.sandbox.overwrite is not already cached in sys.modules. _setup_executor_classes replaces the "deerflow.sandbox" parent package with a MagicMock, so a later `from deerflow.sandbox.overwrite import unwrap_sandbox` inside _harvest_shell_persistence can no longer locate the submodule through the mocked parent ("'deerflow.sandbox' is not a package") when the leaf module is not already in sys.modules. The helper's `except Exception: return None` silently converts that ImportError into an UNKNOWN provenance stamp. Whether the leaf module was cached depended on which tests ran earlier in the session, making the outcome order-dependent: green in CI by collection-order luck, red when the module runs alone or first. Fix it the same way the fixture already handles audit_context and tool_search: preload the real leaf modules (deerflow.sandbox.sandbox_provider and deerflow.sandbox.overwrite) before installing the mocked parent package, pin them in sys.modules for the duration of the test, and restore the previous state afterwards. --- backend/tests/test_subagent_executor.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/backend/tests/test_subagent_executor.py b/backend/tests/test_subagent_executor.py index 246e3f2a6..a6d915693 100644 --- a/backend/tests/test_subagent_executor.py +++ b/backend/tests/test_subagent_executor.py @@ -85,12 +85,16 @@ def _setup_executor_classes(): original_executor = sys.modules.get("deerflow.subagents.executor") original_audit_context = sys.modules.get("deerflow.agents.middlewares.audit_context") original_tool_search = sys.modules.get("deerflow.tools.builtins.tool_search") + original_sandbox_provider = sys.modules.get("deerflow.sandbox.sandbox_provider") + original_sandbox_overwrite = sys.modules.get("deerflow.sandbox.overwrite") # Preload real executor dependencies before replacing their parent packages # with cycle-breaking test doubles. Keeping the concrete leaf modules in # sys.modules makes this fixture independent of test collection order. audit_context_module = importlib.import_module("deerflow.agents.middlewares.audit_context") tool_search_module = importlib.import_module("deerflow.tools.builtins.tool_search") + sandbox_provider_module = importlib.import_module("deerflow.sandbox.sandbox_provider") + sandbox_overwrite_module = importlib.import_module("deerflow.sandbox.overwrite") # Remove mocked executor if exists (from conftest.py) if "deerflow.subagents.executor" in sys.modules: @@ -106,6 +110,8 @@ def _setup_executor_classes(): sys.modules["deerflow.skills.storage"] = storage_module sys.modules["deerflow.agents.middlewares.audit_context"] = audit_context_module sys.modules["deerflow.tools.builtins.tool_search"] = tool_search_module + sys.modules["deerflow.sandbox.sandbox_provider"] = sandbox_provider_module + sys.modules["deerflow.sandbox.overwrite"] = sandbox_overwrite_module # Import real classes inside fixture from langchain_core.messages import AIMessage, HumanMessage, ToolMessage @@ -158,6 +164,14 @@ def _setup_executor_classes(): sys.modules["deerflow.tools.builtins.tool_search"] = original_tool_search else: sys.modules.pop("deerflow.tools.builtins.tool_search", None) + if original_sandbox_provider is not None: + sys.modules["deerflow.sandbox.sandbox_provider"] = original_sandbox_provider + else: + sys.modules.pop("deerflow.sandbox.sandbox_provider", None) + if original_sandbox_overwrite is not None: + sys.modules["deerflow.sandbox.overwrite"] = original_sandbox_overwrite + else: + sys.modules.pop("deerflow.sandbox.overwrite", None) # Helper classes that wrap real classes for testing