deer-flow/backend/tests/test_lark_shim_atomic.py
xiaodu55 9bd0056f3d
test: fix six Windows-host failures across sandbox, read_file, lark shim, skill review, and extension checks (#6787)
* test: fix five Windows-host failures across sandbox, read_file, lark shim, and extension checks

Windows dev hosts currently fail five backend test cases that CI (ubuntu)
never exercises:

- test_local_sandbox_command_timeout: the fixture forces a bare /bin/sh
  shell and the Windows dispatch spawns it directly (WinError 2), and
  make(1) is not a given on Windows. Stub both runners; the contract under
  test is the timeout marker assembly, not the runner.
- test_local_sandbox_provider_mounts: the Popen-capture mock delegated to
  the real spawn, which cannot execute a POSIX shell path on Windows.
  Capture at the runner seam instead and assert the rewritten path
  shape-agnostically (the Windows runners pass the command string, not a
  [shell, -c, command] list).
- test_read_file_tool_binary: text-mode write_text leaks CRLF into the
  fixture on Windows; write the mount fixture as bytes (#6042 follow-up).
- test_lark_shim_atomic: skip the exact 0o755/0o644 assertions on Windows
  hosts, whose stat mode approximates POSIX bits (#6257 pattern).
- test_extension_check_script: zipfile normalizes os.sep in entry names on
  Windows reads, so a writestr-built fixture can never carry a verbatim
  backslash entry there. Inject the hostile name into namelist unless the
  archive already stores it, pinning the guard contract identically.

On a Windows 11 host these five files go from five failed cases to green
(102 passed, ruff check/format clean). Two more Windows-host failures
(skill-review NUL probe, jina shared-deadline classification) need deeper
root-causing and are left for follow-ups.

* test(skill-review): review the committed skill tree, not local residue

The video-generation review test scans the live directory, so untracked
build residue inside skills/public/video-generation fails it: once bytecode
caching drops a scripts/__pycache__ directory into the tree (importing the
skill script writes one), skillscan's package-undecodable-script finding is
correct and the test can never pass again until the residue is deleted by
hand. Reproduced on Windows: a planted scripts/__pycache__ pyc fails the
test; deleting it makes it pass.

- Materialize the tracked skill tree (git ls-files) into a temp dir and
  review that, so the test reviews exactly the committed package.
- tests/skills/skill_loader.py disables bytecode caching around
  exec_module, so the root skill suite stops polluting the committed tree.

Follows up on #6786 (item 6).

* docs(test): state the argv-list contract on both sandbox runners

The Windows dispatch also passes the resolved command as an argv list, so
the string fallback was unreachable on every platform.

---------

Co-authored-by: xiaodu55 <xiaodu55@users.noreply.github.com>
2026-10-14 06:19:43 +08:00

89 lines
3.1 KiB
Python

# SPDX-License-Identifier: MIT
"""The Lark CLI runtime files must never be observable half written."""
from __future__ import annotations
import json
import os
import pytest
from deerflow.integrations.lark_broker import install_shim
def test_install_shim_publishes_executable_files(tmp_path):
launcher = install_shim(str(tmp_path), version="1.2.3")
assert os.access(launcher, os.X_OK)
marker = tmp_path / ".deerflow-lark-cli-runtime.json"
assert json.loads(marker.read_text(encoding="utf-8"))["version"] == "1.2.3"
assert [p.name for p in (tmp_path / "bin").glob("*.tmp")] == []
@pytest.mark.skipif(os.name == "nt", reason="Windows stat mode approximates POSIX permission bits (0o644 for every regular file); exact 0o755/0o644 assertions are POSIX-bound")
def test_install_shim_publishes_correct_permissions(tmp_path):
"""Launcher and shim must be executable (0o755), marker readable (0o644)."""
launcher = install_shim(str(tmp_path), version="1.2.3")
launcher_stat = os.stat(launcher)
assert launcher_stat.st_mode & 0o777 == 0o755
shim_body = tmp_path / "bin" / "lark-cli-shim.py"
shim_stat = os.stat(shim_body)
assert shim_stat.st_mode & 0o777 == 0o755
marker = tmp_path / ".deerflow-lark-cli-runtime.json"
marker_stat = os.stat(marker)
assert marker_stat.st_mode & 0o777 == 0o644
def test_a_failed_publish_leaves_the_previous_runtime_intact(tmp_path, monkeypatch):
install_shim(str(tmp_path), version="1.0.0")
marker = tmp_path / ".deerflow-lark-cli-runtime.json"
before = marker.read_text(encoding="utf-8")
def _boom(*args, **kwargs):
raise OSError("read-only filesystem")
monkeypatch.setattr(os, "replace", _boom)
with pytest.raises(OSError):
install_shim(str(tmp_path), version="2.0.0")
# A truncating open would have emptied the marker before failing, leaving the
# gateway with unparseable JSON; publishing through a temporary file cannot.
assert marker.read_text(encoding="utf-8") == before
def test_atomic_helper_keeps_the_previous_file_when_publish_fails(tmp_path, monkeypatch):
from deerflow.integrations.lark_broker import _write_text_atomically
target = tmp_path / "runtime.json"
target.write_text('{"version": "1.0.0"}', encoding="utf-8")
def _boom(*args, **kwargs):
raise OSError("read-only filesystem")
monkeypatch.setattr(os, "replace", _boom)
with pytest.raises(OSError):
_write_text_atomically(str(target), '{"version": "2.0.0"}', mode=0o644)
assert target.read_text(encoding="utf-8") == '{"version": "1.0.0"}'
assert list(tmp_path.glob("*.tmp")) == []
def test_truncating_open_would_have_destroyed_it(tmp_path):
"""Control: the previous implementation loses the content in the same failure."""
target = tmp_path / "runtime.json"
target.write_text('{"version": "1.0.0"}', encoding="utf-8")
try:
with open(target, "w", encoding="utf-8") as handle:
handle.write('{"version": ')
raise OSError("read-only filesystem")
except OSError:
pass
assert target.read_text(encoding="utf-8") != '{"version": "1.0.0"}'