fix Windows Docker sandbox path mounting (#1634)

* fix windows docker sandbox paths

* fix windows sandbox mount validation

* fix backend checks for windows sandbox path PR
This commit is contained in:
Admire
2026-03-31 22:19:27 +08:00
committed by GitHub
parent c2f7be37b3
commit 3ff15423d6
6 changed files with 157 additions and 26 deletions
+32 -1
View File
@@ -5,7 +5,7 @@ from unittest.mock import MagicMock, patch
import pytest
from deerflow.config.paths import Paths
from deerflow.config.paths import Paths, join_host_path
# ── ensure_thread_dirs ───────────────────────────────────────────────────────
@@ -31,6 +31,13 @@ def test_ensure_thread_dirs_acp_workspace_is_world_writable(tmp_path):
assert mode == oct(0o777)
def test_host_thread_dir_rejects_invalid_thread_id(tmp_path):
paths = Paths(base_dir=tmp_path)
with pytest.raises(ValueError, match="Invalid thread_id"):
paths.host_thread_dir("../escape")
# ── _get_thread_mounts ───────────────────────────────────────────────────────
@@ -75,6 +82,30 @@ def test_get_thread_mounts_includes_user_data_dirs(tmp_path, monkeypatch):
assert "/mnt/user-data/outputs" in container_paths
def test_join_host_path_preserves_windows_drive_letter_style():
base = r"C:\Users\demo\deer-flow\backend\.deer-flow"
joined = join_host_path(base, "threads", "thread-9", "user-data", "outputs")
assert joined == r"C:\Users\demo\deer-flow\backend\.deer-flow\threads\thread-9\user-data\outputs"
def test_get_thread_mounts_preserves_windows_host_path_style(tmp_path, monkeypatch):
"""Docker bind mount sources must keep Windows-style paths intact."""
aio_mod = importlib.import_module("deerflow.community.aio_sandbox.aio_sandbox_provider")
monkeypatch.setenv("DEER_FLOW_HOST_BASE_DIR", r"C:\Users\demo\deer-flow\backend\.deer-flow")
monkeypatch.setattr(aio_mod, "get_paths", lambda: Paths(base_dir=tmp_path))
mounts = aio_mod.AioSandboxProvider._get_thread_mounts("thread-10")
container_paths = {container_path: host_path for host_path, container_path, _ in mounts}
assert container_paths["/mnt/user-data/workspace"] == r"C:\Users\demo\deer-flow\backend\.deer-flow\threads\thread-10\user-data\workspace"
assert container_paths["/mnt/user-data/uploads"] == r"C:\Users\demo\deer-flow\backend\.deer-flow\threads\thread-10\user-data\uploads"
assert container_paths["/mnt/user-data/outputs"] == r"C:\Users\demo\deer-flow\backend\.deer-flow\threads\thread-10\user-data\outputs"
assert container_paths["/mnt/acp-workspace"] == r"C:\Users\demo\deer-flow\backend\.deer-flow\threads\thread-10\acp-workspace"
def test_discover_or_create_only_unlocks_when_lock_succeeds(tmp_path, monkeypatch):
"""Unlock should not run if exclusive locking itself fails."""
aio_mod = importlib.import_module("deerflow.community.aio_sandbox.aio_sandbox_provider")
@@ -11,9 +11,16 @@ import pytest
REPO_ROOT = Path(__file__).resolve().parents[2]
SCRIPT_PATH = REPO_ROOT / "scripts" / "docker.sh"
BASH_EXECUTABLE = which("bash") or r"C:\Program Files\Git\bin\bash.exe"
BASH_CANDIDATES = [
Path(r"C:\Program Files\Git\bin\bash.exe"),
Path(which("bash")) if which("bash") else None,
]
BASH_EXECUTABLE = next(
(str(path) for path in BASH_CANDIDATES if path is not None and path.exists() and "WindowsApps" not in str(path)),
None,
)
if not Path(BASH_EXECUTABLE).exists():
if BASH_EXECUTABLE is None:
pytestmark = pytest.mark.skip(reason="bash is required for docker.sh detection tests")
@@ -21,13 +28,14 @@ def _detect_mode_with_config(config_content: str) -> str:
"""Write config content into a temp project root and execute detect_sandbox_mode."""
with tempfile.TemporaryDirectory() as tmpdir:
tmp_root = Path(tmpdir)
(tmp_root / "config.yaml").write_text(config_content)
(tmp_root / "config.yaml").write_text(config_content, encoding="utf-8")
command = f"source '{SCRIPT_PATH}' && PROJECT_ROOT='{tmp_root}' && detect_sandbox_mode"
output = subprocess.check_output(
[BASH_EXECUTABLE, "-lc", command],
text=True,
encoding="utf-8",
).strip()
return output
@@ -37,7 +45,11 @@ def test_detect_mode_defaults_to_local_when_config_missing():
"""No config file should default to local mode."""
with tempfile.TemporaryDirectory() as tmpdir:
command = f"source '{SCRIPT_PATH}' && PROJECT_ROOT='{tmpdir}' && detect_sandbox_mode"
output = subprocess.check_output([BASH_EXECUTABLE, "-lc", command], text=True).strip()
output = subprocess.check_output(
[BASH_EXECUTABLE, "-lc", command],
text=True,
encoding="utf-8",
).strip()
assert output == "local"