"""Unit tests for the Tenki community sandbox provider. These run in CI without ``tenki-sandbox`` installed: they cover the lazy-import error path, provider lifecycle, path-safety guards, the native ``fs`` file round-trip, warm-pool mechanics, and scope resolution — none of which need a live sandbox. A single opt-in integration test (``test_integration_real_sandbox``) exercises a real Tenki microVM end to end when ``TENKI_API_KEY`` is set. """ from __future__ import annotations import errno import os import shlex import sys import threading import time import types import pytest from deerflow.community.tenki.provider import _BOOTSTRAP_TIMEOUT, TenkiSandboxProvider, _import_client from deerflow.community.tenki.sandbox import TenkiSandbox # ── Fake Tenki SDK ──────────────────────────────────────────────────── class _FakeResult: def __init__(self, exit_code: int = 0, stdout: bytes = b"", stderr: bytes = b"") -> None: self.exit_code = exit_code self.stdout = stdout self.stderr = stderr @property def stdout_text(self) -> str: return self.stdout.decode(errors="replace") @property def stderr_text(self) -> str: return self.stderr.decode(errors="replace") class _FakeSessionTerminatedError(RuntimeError): """Stand-in whose class name matches a Tenki terminal error.""" # Rename so ``type(e).__name__`` matches the adapter's terminal-name set. _FakeSessionTerminatedError.__name__ = "SessionTerminatedError" class _FakeMissingFileError(Exception): """Stand-in for ``tenki_sandbox.errors.FileNotFoundError`` (matched by name).""" _FakeMissingFileError.__name__ = "FileNotFoundError" class _FakeFileInfo: def __init__(self, path: str, size: int) -> None: self.path = path self.size = size self.is_dir = False class _FakeFS: """In-memory stand-in for Tenki's native ``sandbox.fs`` API.""" _READ_FRAME = 64 # small, so multi-frame reads are exercised def __init__(self, owner: _FakeSandbox) -> None: self._owner = owner def _guard(self) -> None: if self._owner.fs_error is not None: raise self._owner.fs_error def mkdir(self, path: str, *, recursive: bool = True, mode: int = 0o755) -> None: self._guard() self._owner.dirs.add(path) def read_bytes(self, path: str) -> bytes: self._guard() if path not in self._owner.files: raise _FakeMissingFileError(f"no such file or directory: {path}") return self._owner.files[path] def read_text(self, path: str, *, encoding: str = "utf-8") -> str: return self.read_bytes(path).decode(encoding, errors="replace") def read_stream(self, path: str, *, offset: int = 0, length: int = 0, chunk_bytes: int = 0): # A generator, like the real SDK: errors surface while iterating. data = self.read_bytes(path) for i in range(0, len(data), self._READ_FRAME): yield data[i : i + self._READ_FRAME] def write_stream(self, path: str, chunks, *, mode: int = 0o644, truncate: bool = True, sync: bool = False) -> None: self._guard() frames = list(chunks) self._owner.write_frame_counts.append(len(frames)) self._owner.files[path] = b"".join(frames) def stat(self, path: str) -> _FakeFileInfo: self._guard() if path not in self._owner.files: raise _FakeMissingFileError(f"no such file or directory: {path}") return _FakeFileInfo(path, len(self._owner.files[path])) class _FakeSandbox: """A fake tenki ``Sandbox``: a native ``fs`` API plus the handful of shell commands the adapter still emits for search, backed by one in-memory filesystem — so file and search round-trips are exercised for real. """ def __init__( self, *, exec_error: Exception | None = None, close_error: Exception | None = None, fs_error: Exception | None = None, wait_ready_error: Exception | None = None, ) -> None: self.id = "remote-session-id" self.files: dict[str, bytes] = {} self.dirs: set[str] = set() self.write_frame_counts: list[int] = [] self.exec_calls: list[dict] = [] self.exec_error = exec_error self.close_error = close_error self.fs_error = fs_error self.wait_ready_error = wait_ready_error self.wait_ready_calls = 0 self.closed = False self.fs = _FakeFS(self) def wait_ready(self, timeout: float = 180) -> None: self.wait_ready_calls += 1 if self.wait_ready_error is not None: raise self.wait_ready_error def exec(self, *argv: str, cwd=None, env=None, timeout=None): self.exec_calls.append({"argv": argv, "cwd": cwd, "env": env, "timeout": timeout}) if self.exec_error is not None: raise self.exec_error if argv[:2] == ("sh", "-lc"): return self._run_script(argv[2]) return _FakeResult() def _run_script(self, script: str) -> _FakeResult: if script == "echo ok": return _FakeResult(stdout=b"ok\n") if "BOOTSTRAP_OK" in script: # provider create-time bootstrap script return _FakeResult(stdout=b"BOOTSTRAP_OK\n") if script.startswith("find "): root = shlex.split(script)[1] hits = [p for p in self.files if p == root or p.startswith(f"{root.rstrip('/')}/")] return _FakeResult(stdout=("\n".join(hits) + "\n").encode() if hits else b"") if script.startswith("grep "): # grep -e 2>/dev/null | head -N tokens = shlex.split(script) needle, root = tokens[tokens.index("-e") + 1], tokens[tokens.index("-e") + 2] lines = [] for path, blob in sorted(self.files.items()): if not path.startswith(root): continue for n, text in enumerate(blob.decode(errors="replace").splitlines(), start=1): if needle in text: lines.append(f"{path}:{n}:{text}") return _FakeResult(stdout=("\n".join(lines) + "\n").encode() if lines else b"") return _FakeResult() def close(self): self.closed = True if self.close_error is not None: raise self.close_error class _FakeProject: def __init__(self, id: str, name: str) -> None: self.id = id self.name = name class _FakeWorkspace: def __init__(self, id: str, name: str, projects: list[_FakeProject]) -> None: self.id = id self.name = name self.projects = projects class _FakeIdentity: def __init__(self, workspaces: list[_FakeWorkspace]) -> None: self.workspaces = workspaces class _FakeClient: def __init__(self, *, workspaces=None, sandbox_factory=None, **kwargs) -> None: self.create_count = 0 self.create_kwargs: list[dict] = [] self._sandbox_factory = sandbox_factory or (lambda: _FakeSandbox()) self.last_sandbox: _FakeSandbox | None = None self._by_id: dict[str, _FakeSandbox] = {} self._workspaces = workspaces if workspaces is not None else [_FakeWorkspace("ws1", "Workspace", [_FakeProject("proj1", "Project")])] def who_am_i(self): return _FakeIdentity(self._workspaces) def create(self, **kwargs): self.create_count += 1 self.create_kwargs.append(kwargs) sandbox = self._sandbox_factory() sandbox.id = f"sb{self.create_count}" self._by_id[sandbox.id] = sandbox self.last_sandbox = sandbox return sandbox def get(self, sandbox_id): return self._by_id[sandbox_id] # ── Config stub ─────────────────────────────────────────────────────── def _stub_config(sandbox_attrs=None): attrs = sandbox_attrs or {} return types.SimpleNamespace(sandbox=types.SimpleNamespace(**attrs)) def _install(monkeypatch, *, client=None, config_attrs=None): """Construct a provider with get_app_config + _import_client stubbed.""" monkeypatch.setattr( "deerflow.community.tenki.provider.get_app_config", lambda: _stub_config(config_attrs), ) if client is not None: monkeypatch.setattr( "deerflow.community.tenki.provider._import_client", lambda: lambda **kw: client, ) provider = TenkiSandboxProvider() return provider def _no_tenki(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setitem(sys.modules, "tenki_sandbox", None) # ── Lazy import ──────────────────────────────────────────────────────── def test_import_client_missing_raises_actionable(monkeypatch: pytest.MonkeyPatch) -> None: _no_tenki(monkeypatch) with pytest.raises(ImportError, match=r"deerflow-harness\[tenki\]"): _import_client() def test_acquire_without_tenki_raises_and_shuts_down_cleanly(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr("deerflow.community.tenki.provider.get_app_config", lambda: _stub_config()) _no_tenki(monkeypatch) provider = TenkiSandboxProvider() try: with pytest.raises(ImportError, match=r"deerflow-harness\[tenki\]"): provider.acquire("thread-1", user_id="u") finally: provider.shutdown() provider.shutdown() # idempotent # ── Adapter: guards, env, output, terminal detection ────────────────── def test_guard_traversal() -> None: assert TenkiSandbox._guard_traversal("/mnt/user-data/workspace/a.txt") == "/mnt/user-data/workspace/a.txt" assert TenkiSandbox._guard_traversal("relative/ok.txt") == "relative/ok.txt" with pytest.raises(PermissionError): TenkiSandbox._guard_traversal("/mnt/user-data/../etc/passwd") with pytest.raises(ValueError): TenkiSandbox._guard_traversal("") def test_resolve_path_remaps_virtual_prefix_to_home() -> None: box = TenkiSandbox("sb", _FakeSandbox(), home_dir="/home/tenki") assert box._resolve_path("/mnt/user-data") == "/home/tenki" assert box._resolve_path("/mnt/user-data/workspace/a.txt") == "/home/tenki/workspace/a.txt" # Non-virtual absolute paths pass through unchanged. assert box._resolve_path("/etc/hostname") == "/etc/hostname" with pytest.raises(PermissionError): box._resolve_path("/mnt/user-data/../etc/passwd") def test_download_file_guards_reject_before_touching_sandbox() -> None: fake = _FakeSandbox() box = TenkiSandbox("sb", fake) with pytest.raises(PermissionError): box.download_file("/etc/passwd") # outside the virtual prefix with pytest.raises(PermissionError): box.download_file("/mnt/user-data/../etc/passwd") # traversal assert fake.exec_calls == [] # guards raised before any exec def test_execute_command_rejects_invalid_env_key() -> None: box = TenkiSandbox("sb", _FakeSandbox()) with pytest.raises(ValueError, match=r"POSIX"): box.execute_command("echo hi", env={"BAD KEY": "x"}) def test_execute_command_formats_stdout_and_forwards_env_timeout() -> None: fake = _FakeSandbox() box = TenkiSandbox("sb", fake, default_env={"BASE": "1"}) out = box.execute_command("echo ok", env={"EXTRA": "2"}, timeout=5) assert out == "ok\n" # combined output is returned verbatim (matches boxlite/e2b) call = fake.exec_calls[-1] assert call["argv"] == ("sh", "-lc", "echo ok") assert call["env"] == {"BASE": "1", "EXTRA": "2"} assert call["timeout"] == 5 assert call["cwd"] is None # no forced cwd; runs in sandbox default dir def test_execute_command_returns_error_as_text() -> None: box = TenkiSandbox("sb", _FakeSandbox(exec_error=RuntimeError("boom"))) assert box.execute_command("echo hi") == "Error: boom" def test_execute_command_closed_returns_error() -> None: box = TenkiSandbox("sb", _FakeSandbox()) box.close() assert box.execute_command("echo hi") == "Error: sandbox has been closed" def test_terminal_failure_triggers_callback() -> None: invalidated: list[tuple[str, str]] = [] box = TenkiSandbox( "sb", _FakeSandbox(exec_error=_FakeSessionTerminatedError("session gone")), on_terminal_failure=lambda sid, reason: invalidated.append((sid, reason)), ) out = box.execute_command("echo hi") assert out == "Error: session gone" assert invalidated == [("sb", "session gone")] def test_regular_error_does_not_trigger_terminal_callback() -> None: invalidated: list[tuple[str, str]] = [] box = TenkiSandbox( "sb", _FakeSandbox(exec_error=RuntimeError("user command failed")), on_terminal_failure=lambda sid, reason: invalidated.append((sid, reason)), ) box.execute_command("echo hi") assert invalidated == [] def test_transient_transport_error_is_not_retried_and_not_evicted() -> None: # exec is not idempotent, so a transient transport error must NOT be retried # (re-running could double a command's side effects or duplicate a base64 # write chunk). It surfaces as text, and — not being a terminal session # error — does not evict the sandbox. invalidated: list[tuple[str, str]] = [] fake = _FakeSandbox(exec_error=RuntimeError("UNAVAILABLE: Socket closed")) box = TenkiSandbox("sb", fake, on_terminal_failure=lambda sid, reason: invalidated.append((sid, reason))) out = box.execute_command("echo ok") assert out.startswith("Error:") assert len(fake.exec_calls) == 1 # executed exactly once — no retry assert invalidated == [] # transient, not terminal → sandbox not evicted def test_connection_error_is_terminal() -> None: invalidated: list[tuple[str, str]] = [] box = TenkiSandbox( "sb", _FakeSandbox(exec_error=ConnectionError("reset")), on_terminal_failure=lambda sid, reason: invalidated.append((sid, reason)), ) box.execute_command("echo hi") assert invalidated == [("sb", "reset")] def test_close_is_idempotent() -> None: fake = _FakeSandbox() box = TenkiSandbox("sb", fake) box.close() box.close() # idempotent assert box.is_closed is True assert fake.closed is True def test_close_failure_leaves_sandbox_retryable() -> None: # Marking the adapter closed before a failed termination would strand a # running (billed) microVM with no handle left to retry it. fake = _FakeSandbox(close_error=RuntimeError("terminate rejected")) box = TenkiSandbox("sb", fake) with pytest.raises(RuntimeError, match="terminate rejected"): box.close() assert box.is_closed is False # still ours to terminate fake.close_error = None box.close() assert box.is_closed is True def test_close_treats_terminal_error_as_already_gone() -> None: fake = _FakeSandbox(close_error=_FakeSessionTerminatedError("session gone")) box = TenkiSandbox("sb", fake) box.close() # nothing left to terminate → not an error assert box.is_closed is True # ── Adapter: native fs file round-trip ──────────────────────────────── def test_file_round_trip_text() -> None: box = TenkiSandbox("sb", _FakeSandbox()) box.write_file("/mnt/user-data/workspace/note.txt", "hello world") assert box.read_file("/mnt/user-data/workspace/note.txt") == "hello world" def test_write_creates_parent_directory() -> None: fake = _FakeSandbox() box = TenkiSandbox("sb", fake) box.write_file("/mnt/user-data/workspace/nested/note.txt", "hi") assert "/home/tenki/workspace/nested" in fake.dirs def test_file_round_trip_large_binary_streams_in_frames() -> None: fake = _FakeSandbox() box = TenkiSandbox("sb", fake) data = bytes(range(256)) * 8192 # 2 MB → more than one 1 MiB upload frame box.update_file("/mnt/user-data/outputs/blob.bin", data) assert fake.write_frame_counts[-1] > 1 assert box.download_file("/mnt/user-data/outputs/blob.bin") == data def test_append_accumulates() -> None: box = TenkiSandbox("sb", _FakeSandbox()) box.write_file("/mnt/user-data/workspace/log.txt", "a") box.write_file("/mnt/user-data/workspace/log.txt", "b", append=True) assert box.read_file("/mnt/user-data/workspace/log.txt") == "ab" def test_append_to_missing_file_creates_it() -> None: box = TenkiSandbox("sb", _FakeSandbox()) box.write_file("/mnt/user-data/workspace/new.txt", "first", append=True) assert box.read_file("/mnt/user-data/workspace/new.txt") == "first" def test_read_missing_file_returns_error() -> None: box = TenkiSandbox("sb", _FakeSandbox()) assert box.read_file("/mnt/user-data/workspace/nope.txt").startswith("Error:") def test_download_missing_file_raises_oserror() -> None: box = TenkiSandbox("sb", _FakeSandbox()) with pytest.raises(OSError): box.download_file("/mnt/user-data/outputs/nope.bin") def test_download_cap_counts_bytes_actually_received(monkeypatch) -> None: # The cap must not rely on a separate size probe: a file that grows between # the probe and the read would slip past it. monkeypatch.setattr("deerflow.community.tenki.sandbox._MAX_DOWNLOAD_SIZE", 100) fake = _FakeSandbox() box = TenkiSandbox("sb", fake) fake.files["/home/tenki/outputs/big.bin"] = b"x" * 500 with pytest.raises(OSError) as excinfo: box.download_file("/mnt/user-data/outputs/big.bin") assert excinfo.value.errno == errno.EFBIG def test_download_size_cap_does_not_evict_sandbox(monkeypatch) -> None: # Hitting the size cap is a client-side limit, not a dead session — the # sandbox must stay live. monkeypatch.setattr("deerflow.community.tenki.sandbox._MAX_DOWNLOAD_SIZE", 100) invalidated: list[tuple[str, str]] = [] fake = _FakeSandbox() box = TenkiSandbox("sb", fake, on_terminal_failure=lambda sid, reason: invalidated.append((sid, reason))) fake.files["/home/tenki/outputs/big.bin"] = b"x" * 500 with pytest.raises(OSError): box.download_file("/mnt/user-data/outputs/big.bin") assert invalidated == [] def test_download_terminal_transport_error_evicts_sandbox() -> None: # A ConnectionError mid-stream is an OSError subclass and terminal; the old # `except OSError: raise` re-raised it before _note_failure, so a session # that died mid-download never got evicted. invalidated: list[tuple[str, str]] = [] fake = _FakeSandbox() box = TenkiSandbox("sb", fake, on_terminal_failure=lambda sid, reason: invalidated.append((sid, reason))) box.write_file("/mnt/user-data/outputs/f.bin", "payload") fake.fs_error = ConnectionError("connection reset mid-stream") with pytest.raises(ConnectionError): box.download_file("/mnt/user-data/outputs/f.bin") assert invalidated == [("sb", "connection reset mid-stream")] def test_fs_terminal_failure_evicts_sandbox() -> None: invalidated: list[tuple[str, str]] = [] fake = _FakeSandbox(fs_error=_FakeSessionTerminatedError("session gone")) box = TenkiSandbox("sb", fake, on_terminal_failure=lambda sid, reason: invalidated.append((sid, reason))) with pytest.raises(Exception, match="session gone"): box.write_file("/mnt/user-data/workspace/a.txt", "x") assert invalidated == [("sb", "session gone")] # ── Adapter: returned paths stay caller-facing ──────────────────────── def test_search_results_use_virtual_paths() -> None: box = TenkiSandbox("sb", _FakeSandbox()) box.write_file("/mnt/user-data/workspace/pkg/mod.py", "def foo():\n return 42\n") assert box.list_dir("/mnt/user-data/workspace") == ["/mnt/user-data/workspace/pkg/mod.py"] found, truncated = box.glob("/mnt/user-data/workspace", "**/*.py") assert found == ["/mnt/user-data/workspace/pkg/mod.py"] and not truncated matches, _ = box.grep("/mnt/user-data/workspace", "return") assert [m.path for m in matches] == ["/mnt/user-data/workspace/pkg/mod.py"] assert matches[0].line_number == 2 # Results feed straight back into the file APIs. assert box.read_file(found[0]).startswith("def foo()") def test_grep_glob_keeps_its_directory_prefix() -> None: box = TenkiSandbox("sb", _FakeSandbox()) box.write_file("/mnt/user-data/workspace/src/a.js", "const x = 1;\n") box.write_file("/mnt/user-data/workspace/vendor/b.js", "const x = 2;\n") matches, _ = box.grep("/mnt/user-data/workspace", "const x", glob="src/*.js") assert [m.path for m in matches] == ["/mnt/user-data/workspace/src/a.js"] def test_grep_passes_capital_h_so_single_file_matches_parse() -> None: # Without -H, `grep -r` on a path that resolves to a single file prints # "line:text" and the file:line:text unpack drops every match. The flag must # always be present regardless of the other options. fake = _FakeSandbox() box = TenkiSandbox("sb", fake) box.write_file("/mnt/user-data/workspace/a.txt", "needle here\n") box.grep("/mnt/user-data/workspace", "needle") grep_scripts = [c["argv"][2] for c in fake.exec_calls if c["argv"][:2] == ("sh", "-lc") and c["argv"][2].startswith("grep ")] assert grep_scripts and "-H" in shlex.split(grep_scripts[0]) def test_grep_single_file_path_with_matching_glob() -> None: box = TenkiSandbox("sb", _FakeSandbox()) box.write_file("/mnt/user-data/workspace/a.txt", "needle here\n") matches, truncated = box.grep("/mnt/user-data/workspace/a.txt", "needle", glob="*.txt") assert [m.path for m in matches] == ["/mnt/user-data/workspace/a.txt"] assert truncated is False def _grep_script(fake: _FakeSandbox) -> list[str]: scripts = [c["argv"][2] for c in fake.exec_calls if c["argv"][:2] == ("sh", "-lc") and c["argv"][2].startswith("grep ")] assert scripts, "no grep command was issued" return shlex.split(scripts[0]) def test_grep_literal_uses_fixed_string_flag() -> None: fake = _FakeSandbox() box = TenkiSandbox("sb", fake) box.write_file("/mnt/user-data/workspace/a.txt", "a.b\n") box.grep("/mnt/user-data/workspace", "a.b", literal=True) tokens = _grep_script(fake) assert "-F" in tokens and "-E" not in tokens # -F matches the pattern literally assert "-i" in tokens # case-insensitive is still the default def test_grep_case_sensitive_omits_ignore_case_flag() -> None: fake = _FakeSandbox() box = TenkiSandbox("sb", fake) box.write_file("/mnt/user-data/workspace/a.txt", "Needle\n") box.grep("/mnt/user-data/workspace", "Needle", case_sensitive=True) tokens = _grep_script(fake) assert "-i" not in tokens # case-sensitive → no -i assert "-E" in tokens def test_glob_include_dirs_adds_directory_type_to_find() -> None: fake = _FakeSandbox() box = TenkiSandbox("sb", fake) box.glob("/mnt/user-data/workspace", "*", include_dirs=True) find_scripts = [c["argv"][2] for c in fake.exec_calls if c["argv"][:2] == ("sh", "-lc") and c["argv"][2].startswith("find ")] assert find_scripts and "-type d" in find_scripts[-1] # dirs requested, not just files def test_list_dir_forwards_max_depth() -> None: fake = _FakeSandbox() box = TenkiSandbox("sb", fake) box.list_dir("/mnt/user-data/workspace", max_depth=4) find_scripts = [c["argv"][2] for c in fake.exec_calls if c["argv"][:2] == ("sh", "-lc") and c["argv"][2].startswith("find ")] assert find_scripts and "-maxdepth 4" in find_scripts[-1] def test_paths_outside_the_virtual_prefix_are_reported_as_is() -> None: box = TenkiSandbox("sb", _FakeSandbox()) assert box._virtual_path("/etc/hostname") == "/etc/hostname" assert box._virtual_path("/home/tenki") == "/mnt/user-data" # ── Provider: id derivation ─────────────────────────────────────────── def test_sandbox_id_deterministic(monkeypatch): provider = _install(monkeypatch) assert provider._sandbox_id("t1", "u1") == provider._sandbox_id("t1", "u1") assert len(provider._sandbox_id("t1", "u1")) == 16 # 64-bit, like community/e2b_sandbox def test_sandbox_id_distinct_users_and_threads(monkeypatch): provider = _install(monkeypatch) assert provider._sandbox_id("t1", "u1") != provider._sandbox_id("t1", "u2") assert provider._sandbox_id("t1", "u1") != provider._sandbox_id("t2", "u1") def test_idle_timeout_zero_disables_reaper(monkeypatch): provider = _install(monkeypatch, config_attrs={"idle_timeout": 0}) assert provider._config["idle_timeout"] == 0 assert provider._idle_checker_thread is None provider.shutdown() def test_load_config_rejects_invalid_environment_key(monkeypatch): # The config `environment` is merged into every command; a bad key must # fail fast at load time, like the per-call env in execute_command, not # surface as a confusing SDK error at create/exec time. monkeypatch.setattr("deerflow.community.tenki.provider.get_app_config", lambda: _stub_config({"environment": {"bad-key": "1"}})) with pytest.raises(ValueError, match=r"POSIX"): TenkiSandboxProvider() def test_load_config_accepts_valid_environment_key(monkeypatch): provider = _install(monkeypatch, config_attrs={"environment": {"PYTHONUNBUFFERED": "1"}}) assert provider._config["environment"] == {"PYTHONUNBUFFERED": "1"} provider.shutdown() # ── Provider: acquire / create / warm pool ──────────────────────────── def test_create_passes_prefixed_name_and_scope(monkeypatch): client = _FakeClient() provider = _install(monkeypatch, client=client) sid = provider.acquire("thread-1", user_id="u1") assert sid in provider._sandboxes kwargs = client.create_kwargs[0] assert kwargs["name"].startswith("deer-flow-tenki-") assert kwargs["project_id"] == "proj1" assert kwargs["workspace_id"] == "ws1" provider.shutdown() def test_create_waits_client_side_and_configures_lifetime(monkeypatch): client = _FakeClient() provider = _install(monkeypatch, client=client, config_attrs={"max_duration": 7200}) provider.acquire("thread-1", user_id="u1") kwargs = client.create_kwargs[0] # wait=False keeps the handle on our side of create() so a readiness failure # is still terminable (see the next test). assert kwargs["wait"] is False assert client.last_sandbox.wait_ready_calls == 1 # Without an explicit lifetime, Tenki reaps the sandbox out from under a # long-lived thread at its default (~30 min). assert kwargs["max_duration"] == 7200 assert kwargs["sticky"] is False provider.shutdown() def test_create_default_lifetime_is_explicit(monkeypatch): client = _FakeClient() provider = _install(monkeypatch, client=client) provider.acquire("thread-1", user_id="u1") assert client.create_kwargs[0]["max_duration"] == pytest.approx(4 * 60 * 60) provider.shutdown() def test_create_terminates_the_microvm_when_readiness_fails(monkeypatch): client = _FakeClient(sandbox_factory=lambda: _FakeSandbox(wait_ready_error=RuntimeError("never became ready"))) provider = _install(monkeypatch, client=client) with pytest.raises(RuntimeError, match="never became ready"): provider.acquire("thread-1", user_id="u1") # The session exists remotely even though create() failed — it must not leak. assert client.last_sandbox.closed is True assert provider._sandboxes == {} provider.shutdown() def test_create_survives_bootstrap_failure(monkeypatch, caplog): # Bootstrap is best-effort: the file APIs still work via the home remap, so a # non-zero bootstrap must warn, not fail the acquire. class _BootstrapFailSandbox(_FakeSandbox): def _run_script(self, script: str) -> _FakeResult: if "BOOTSTRAP_OK" in script: return _FakeResult(exit_code=1, stderr=b"permission denied") return super()._run_script(script) client = _FakeClient(sandbox_factory=lambda: _BootstrapFailSandbox()) provider = _install(monkeypatch, client=client) with caplog.at_level("WARNING"): sid = provider.acquire("thread-1", user_id="u1") box = provider.get(sid) assert box is not None and not box.is_closed # usable despite bootstrap failure assert any("bootstrap" in r.message.lower() for r in caplog.records) provider.shutdown() def test_bootstrap_is_non_interactive_and_time_bounded(monkeypatch): # The bootstrap runs under the per-scope acquire lock, so it must not hang: # `sudo -n` fails fast instead of blocking on a password prompt, and the exec # carries a timeout so any other stall drops to the warning path. client = _FakeClient() provider = _install(monkeypatch, client=client) provider.acquire("thread-1", user_id="u1") bootstrap = next(c for c in client.last_sandbox.exec_calls if "BOOTSTRAP_OK" in (c["argv"][2] if len(c["argv"]) > 2 else "")) assert "sudo -n ln -sfn" in bootstrap["argv"][2] # Pin the actual bounded value, not merely "some timeout": a regression to # timeout=0 (no timeout in some SDKs) would slip past an `is not None` check. assert bootstrap["timeout"] == _BOOTSTRAP_TIMEOUT provider.shutdown() def test_release_parks_in_warm_pool(monkeypatch): client = _FakeClient() provider = _install(monkeypatch, client=client) sid = provider.acquire("thread-1", user_id="u1") provider.release(sid) assert sid not in provider._sandboxes assert sid in provider._warm_pool sandbox, _ = provider._warm_pool[sid] assert not sandbox.is_closed # microVM not terminated provider.shutdown() def test_acquire_reclaims_from_warm_pool(monkeypatch): client = _FakeClient() provider = _install(monkeypatch, client=client) sid1 = provider.acquire("thread-1", user_id="u1") provider.release(sid1) sid2 = provider.acquire("thread-1", user_id="u1") assert sid1 == sid2 assert client.create_count == 1 # reused, not recreated assert sid2 in provider._sandboxes provider.shutdown() def test_different_threads_dont_reclaim_each_other(monkeypatch): client = _FakeClient() provider = _install(monkeypatch, client=client) sid_a = provider.acquire("thread-a", user_id="u1") provider.release(sid_a) sid_b = provider.acquire("thread-b", user_id="u1") assert sid_b != sid_a assert sid_a in provider._warm_pool assert sid_b in provider._sandboxes provider.shutdown() def test_warm_pool_reclaim_failed_health_check_creates_new(monkeypatch): client = _FakeClient() provider = _install(monkeypatch, client=client) sid1 = provider.acquire("thread-1", user_id="u1") provider.release(sid1) # Kill the warm sandbox so the health check fails. sandbox, _ = provider._warm_pool[sid1] sandbox.close() sid2 = provider.acquire("thread-1", user_id="u1") assert sid2 == sid1 # same deterministic id assert client.create_count == 2 # a fresh sandbox was created replacement = provider.get(sid2) assert replacement is not None and not replacement.is_closed provider.shutdown() def test_terminal_failure_evicts_active_sandbox(monkeypatch): client = _FakeClient() provider = _install(monkeypatch, client=client) sid = provider.acquire("thread-1", user_id="u1") box = provider.get(sid) assert box is not None # Only fail command-time, not the create-time mkdir bootstrap. client.last_sandbox.exec_error = _FakeSessionTerminatedError("gone") box.execute_command("echo hi") # terminal failure → invalidate assert provider.get(sid) is None provider.shutdown() def test_concurrent_same_thread_acquire_creates_one_sandbox(monkeypatch): client = _FakeClient() provider = _install(monkeypatch, client=client) original_create = provider._create_sandbox create_started = threading.Event() def slow_create(sandbox_id: str): create_started.set() time.sleep(0.1) return original_create(sandbox_id) provider._create_sandbox = slow_create # type: ignore[method-assign] results: list[str] = [] def worker(): results.append(provider.acquire("thread-1", user_id="u1")) first = threading.Thread(target=worker) second = threading.Thread(target=worker) first.start() assert create_started.wait(timeout=2) second.start() first.join(timeout=2) second.join(timeout=2) assert len(results) == 2 assert results[0] == results[1] assert client.create_count == 1 provider.shutdown() def test_release_during_shutdown_closes_instead_of_reparking(monkeypatch): client = _FakeClient() provider = _install(monkeypatch, client=client) sid = provider.acquire("thread-1", user_id="u1") box = provider._sandboxes[sid] with provider._lock: provider._shutdown_called = True provider.release(sid) assert sid not in provider._sandboxes assert sid not in provider._warm_pool assert box.is_closed def test_reset_parks_running_for_later_cleanup(monkeypatch): client = _FakeClient() provider = _install(monkeypatch, client=client) sid_active = provider.acquire("thread-active", user_id="u1") sid_warm = provider.acquire("thread-warm", user_id="u1") provider.release(sid_warm) active_box = provider._sandboxes[sid_active] provider.reset() assert provider._sandboxes == {} assert provider._warm_pool[sid_active][0] is active_box assert provider._thread_sandboxes == {} assert not active_box.is_closed provider.shutdown() assert active_box.is_closed def test_idle_reaper_destroys_expired_warm(monkeypatch): client = _FakeClient() monkeypatch.setattr(TenkiSandboxProvider, "IDLE_CHECK_INTERVAL", 0.1) provider = _install(monkeypatch, client=client) sid = provider.acquire("thread-1", user_id="u1") provider.release(sid) warm_box = provider._warm_pool[sid][0] provider._warm_pool[sid] = (warm_box, time.time() - 9999) time.sleep(0.3) assert sid not in provider._warm_pool assert warm_box.is_closed provider.shutdown() def test_replica_enforcement_evicts_oldest_warm(monkeypatch): client = _FakeClient() provider = _install(monkeypatch, client=client, config_attrs={"replicas": 2}) sid_a = provider.acquire("thread-a", user_id="u1") provider.release(sid_a) sid_b = provider.acquire("thread-b", user_id="u1") provider.release(sid_b) box_a = provider._warm_pool[sid_a][0] provider._warm_pool[sid_a] = (box_a, time.time() - 100) provider._warm_pool[sid_b] = (provider._warm_pool[sid_b][0], time.time()) provider.acquire("thread-c", user_id="u1") assert sid_a not in provider._warm_pool assert box_a.is_closed assert sid_b in provider._warm_pool provider.shutdown() def test_shutdown_destroys_all_and_stops_reaper(monkeypatch): client = _FakeClient() provider = _install(monkeypatch, client=client) sid_active = provider.acquire("thread-1", user_id="u1") sid_warm = provider.acquire("thread-2", user_id="u1") provider.release(sid_warm) box_active = provider._sandboxes[sid_active] box_warm = provider._warm_pool[sid_warm][0] checker = provider._idle_checker_thread provider.shutdown() assert provider._idle_checker_stop.is_set() assert checker is not None and not checker.is_alive() assert box_active.is_closed and box_warm.is_closed assert provider._sandboxes == {} and provider._warm_pool == {} # ── Provider: scope resolution ───────────────────────────────────────── def test_scope_auto_resolves_single(monkeypatch): client = _FakeClient() provider = _install(monkeypatch, client=client) provider.acquire("thread-1", user_id="u1") assert client.create_kwargs[0]["project_id"] == "proj1" assert client.create_kwargs[0]["workspace_id"] == "ws1" provider.shutdown() def test_explicit_project_id_skips_lookup(monkeypatch): client = _FakeClient() provider = _install(monkeypatch, client=client, config_attrs={"project_id": "explicit"}) provider.acquire("thread-1", user_id="u1") assert client.create_kwargs[0]["project_id"] == "explicit" provider.shutdown() def test_ambiguous_project_raises(monkeypatch): client = _FakeClient(workspaces=[_FakeWorkspace("ws1", "W", [_FakeProject("p1", "A"), _FakeProject("p2", "B")])]) provider = _install(monkeypatch, client=client) with pytest.raises(ValueError, match="project_id"): provider.acquire("thread-1", user_id="u1") provider.shutdown() # ── Live integration (opt-in) ────────────────────────────────────────── @pytest.mark.skipif( not os.getenv("TENKI_API_KEY") and not os.getenv("TENKI_AUTH_TOKEN"), reason="requires a real Tenki API key (TENKI_API_KEY); network integration test", ) def test_integration_real_sandbox(monkeypatch): monkeypatch.setattr("deerflow.community.tenki.provider.get_app_config", lambda: _stub_config()) provider = TenkiSandboxProvider() try: sid = provider.acquire("it-thread", user_id="it-user") box = provider.get(sid) assert box is not None assert "42" in box.execute_command("python3 -c 'print(6 * 7)'") box.write_file("/mnt/user-data/workspace/it.txt", "tenki-e2e") assert box.read_file("/mnt/user-data/workspace/it.txt") == "tenki-e2e" finally: provider.shutdown()