mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 21:26:39 +00:00
* fix(config): avoid Unicode failure after generating configuration * fix(setup): handle legacy stdout encodings in setup commands --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
147 lines
5.4 KiB
Python
147 lines
5.4 KiB
Python
"""Exercise setup command output through real, strictly encoded stdout streams."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import textwrap
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
ENCODINGS = ["ascii", "cp1252", "cp936", "utf-8"]
|
|
|
|
|
|
@pytest.fixture
|
|
def setup_checkout(tmp_path: Path) -> Path:
|
|
scripts = tmp_path / "scripts"
|
|
scripts.mkdir()
|
|
for name in ("doctor.py", "setup_wizard.py"):
|
|
shutil.copyfile(REPO_ROOT / "scripts" / name, scripts / name)
|
|
shutil.copytree(REPO_ROOT / "scripts" / "wizard", scripts / "wizard")
|
|
(tmp_path / ".env.example").write_text("# test environment\n", encoding="utf-8")
|
|
(tmp_path / "frontend").mkdir()
|
|
(tmp_path / "frontend" / ".env.example").write_text("# frontend environment\n", encoding="utf-8")
|
|
return tmp_path
|
|
|
|
|
|
def run_setup_script(checkout: Path, encoding: str, code: str) -> subprocess.CompletedProcess[str]:
|
|
return subprocess.run(
|
|
[sys.executable, "-c", textwrap.dedent(code)],
|
|
cwd=checkout,
|
|
env={**os.environ, "PYTHONPATH": str(checkout / "scripts"), "PYTHONIOENCODING": f"{encoding}:strict"},
|
|
capture_output=True,
|
|
text=True,
|
|
encoding=encoding,
|
|
check=False,
|
|
timeout=30,
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("encoding", ENCODINGS)
|
|
@pytest.mark.parametrize("is_tty", [False, True], ids=["pipe", "tty"])
|
|
@pytest.mark.parametrize("has_failure", [False, True], ids=["ready", "failed"])
|
|
def test_doctor_report_survives_restricted_stdout(setup_checkout: Path, encoding: str, is_tty: bool, has_failure: bool):
|
|
result = run_setup_script(
|
|
setup_checkout,
|
|
encoding,
|
|
f"""
|
|
import sys
|
|
from unittest.mock import patch
|
|
import doctor
|
|
|
|
sys.stdout.isatty = lambda: {is_tty!r}
|
|
checks = {{}}
|
|
for name in vars(doctor):
|
|
if name.startswith("check_"):
|
|
value = doctor.CheckResult("Example check", "ok", "Unicode detail: 检查")
|
|
if name in ("check_llm_api_key", "check_llm_auth", "check_llm_package", "check_sandbox"):
|
|
value = [value]
|
|
checks[name] = lambda *args, value=value: value
|
|
checks["check_node"] = lambda: doctor.CheckResult("Warning check", "warn")
|
|
checks["check_pnpm"] = lambda: doctor.CheckResult("Skipped check", "skip")
|
|
checks["check_nginx"] = lambda: doctor.CheckResult(
|
|
"Required check", "fail" if {has_failure!r} else "ok", fix="Run make setup"
|
|
)
|
|
with patch.multiple(doctor, **checks), patch.object(doctor, "resolve_config_path", return_value=(None, None)):
|
|
sys.exit(doctor.main())
|
|
""",
|
|
)
|
|
|
|
assert result.returncode == int(has_failure), result.stderr
|
|
assert result.stderr == ""
|
|
assert "DeerFlow Health Check" in result.stdout
|
|
assert "Skipped check" in result.stdout
|
|
assert "Run make setup" in result.stdout
|
|
assert "Status: " in result.stdout
|
|
assert ("error(s)" if has_failure else "Ready") in result.stdout
|
|
if encoding == "utf-8":
|
|
assert "✓" in result.stdout
|
|
assert "检查" in result.stdout
|
|
|
|
|
|
@pytest.mark.parametrize("encoding", ENCODINGS)
|
|
def test_interactive_setup_completes_with_restricted_stdout(setup_checkout: Path, encoding: str):
|
|
result = run_setup_script(
|
|
setup_checkout,
|
|
encoding,
|
|
"""
|
|
import builtins
|
|
import getpass
|
|
import sys
|
|
import setup_wizard
|
|
from wizard.providers import LLM_PROVIDERS, SEARCH_PROVIDERS, WEB_FETCH_PROVIDERS
|
|
|
|
# Model a legacy-locale terminal while keeping this test portable.
|
|
sys.stdin.isatty = lambda: True
|
|
sys.stdout.isatty = lambda: True
|
|
choices = iter([
|
|
str(next(i for i, provider in enumerate(LLM_PROVIDERS, 1) if provider.name == "openai")),
|
|
"", # default model
|
|
str(len(SEARCH_PROVIDERS) + 1), # skip web search
|
|
str(len(WEB_FETCH_PROVIDERS) + 1), # skip web fetch
|
|
"", # local sandbox
|
|
"", # bash disabled
|
|
"", # file writes enabled
|
|
"", # no IM channels
|
|
])
|
|
builtins.input = lambda prompt: next(choices)
|
|
getpass.getpass = lambda prompt: "test-api-key"
|
|
sys.exit(setup_wizard.main())
|
|
""",
|
|
)
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
assert result.stderr == ""
|
|
assert "Writing configuration" in result.stdout
|
|
assert "Setup complete!" in result.stdout
|
|
assert "make doctor" in result.stdout
|
|
assert (setup_checkout / "config.yaml").is_file()
|
|
assert "OPENAI_API_KEY=test-api-key" in (setup_checkout / ".env").read_text(encoding="utf-8")
|
|
assert (setup_checkout / "frontend" / ".env").read_bytes() == (setup_checkout / "frontend" / ".env.example").read_bytes()
|
|
if encoding == "utf-8":
|
|
assert "✓" in result.stdout
|
|
assert "· Writing configuration" in result.stdout
|
|
|
|
|
|
@pytest.mark.parametrize("encoding", ENCODINGS)
|
|
def test_setup_still_requires_an_interactive_terminal(setup_checkout: Path, encoding: str):
|
|
result = run_setup_script(
|
|
setup_checkout,
|
|
encoding,
|
|
"""
|
|
import sys
|
|
import setup_wizard
|
|
sys.exit(setup_wizard.main())
|
|
""",
|
|
)
|
|
|
|
assert result.returncode == 1
|
|
assert result.stderr == ""
|
|
assert "Non-interactive environment detected" in result.stdout
|
|
assert not (setup_checkout / "config.yaml").exists()
|
|
assert not (setup_checkout / ".env").exists()
|