mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-11-12 22:20:33 +00:00
fix(scripts): make check.py and setup_wizard.py --help print usage (#8152)
Both entry points ignored their arguments entirely, so the first thing a contributor tries ran the real work: `python scripts/check.py --help` ran the full dependency probe (and, on a host with a missing tool, exited 1), and `python scripts/setup_wizard.py --help` entered the wizard -- which on an existing config.yaml asks "Do you want to reconfigure?" and then rewrites config.yaml and .env. Unknown flags were silently ignored the same way. Parse arguments in the __main__ block, following scripts/doctor.py: main() stays directly callable for the in-process unit tests, --help prints usage and exits 0 before any probe or prompt, and an unknown flag exits 2 with "unrecognized arguments". The no-argument invocations behind `make check` and `make setup` are unchanged. Adds subprocess CLI-contract cases in a disposable checkout next to the existing script tests, modelled on backend/tests/test_doctor_script.py.
This commit is contained in:
parent
fc7c7e012c
commit
2baadff446
@ -1,6 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@ -192,3 +194,54 @@ def test_check_preserves_unicode_pnpm_error(tmp_path, monkeypatch, host_encoding
|
||||
def test_check_replaces_invalid_tool_output_bytes():
|
||||
script = _load_script(CHECK_SCRIPT_PATH, "check_invalid_tool_bytes")
|
||||
assert script.run_command([sys.executable, "-c", "import sys; sys.stdout.buffer.write(b'v22.0.0\\xff')"]) == "v22.0.0\ufffd"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def check_checkout(tmp_path: Path) -> Path:
|
||||
checkout = tmp_path / "checkout"
|
||||
scripts_dir = checkout / "scripts"
|
||||
scripts_dir.mkdir(parents=True)
|
||||
(checkout / "frontend").mkdir()
|
||||
for name in ("check.py", "pnpm.py"):
|
||||
shutil.copyfile(REPO_ROOT / "scripts" / name, scripts_dir / name)
|
||||
return checkout
|
||||
|
||||
|
||||
def _run_check(checkout: Path, *args: str) -> subprocess.CompletedProcess[str]:
|
||||
env = {name: value for name, value in os.environ.items() if not name.startswith("DEER_FLOW_") and name not in {"PYTHONPATH", "PYTHONHOME"}}
|
||||
return subprocess.run(
|
||||
[sys.executable, str(checkout / "scripts" / "check.py"), *args],
|
||||
cwd=checkout,
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
check=False,
|
||||
timeout=120,
|
||||
)
|
||||
|
||||
|
||||
def test_help_prints_usage_without_running_the_check(check_checkout: Path):
|
||||
result = _run_check(check_checkout, "--help")
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert "usage:" in result.stdout
|
||||
assert "Checking Required Dependencies" not in result.stdout
|
||||
|
||||
|
||||
def test_unknown_flag_exits_2_with_usage_error(check_checkout: Path):
|
||||
result = _run_check(check_checkout, "--verbos")
|
||||
|
||||
assert result.returncode == 2
|
||||
assert "unrecognized arguments: --verbos" in result.stderr
|
||||
assert "Checking Required Dependencies" not in result.stdout
|
||||
|
||||
|
||||
def test_no_arguments_still_runs_the_check(check_checkout: Path):
|
||||
result = _run_check(check_checkout)
|
||||
|
||||
# The disposable checkout inherits this host's toolchain, so the probes may
|
||||
# pass or fail; either exit code must still come with the normal report.
|
||||
assert result.returncode in (0, 1), result.stderr
|
||||
assert "Checking Required Dependencies" in result.stdout
|
||||
|
||||
63
backend/tests/test_setup_wizard_script.py
Normal file
63
backend/tests/test_setup_wizard_script.py
Normal file
@ -0,0 +1,63 @@
|
||||
"""CLI contract for scripts/setup_wizard.py, exercised in a disposable checkout."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def wizard_checkout(tmp_path: Path) -> Path:
|
||||
checkout = tmp_path / "checkout"
|
||||
scripts_dir = checkout / "scripts"
|
||||
scripts_dir.mkdir(parents=True)
|
||||
shutil.copyfile(REPO_ROOT / "scripts" / "setup_wizard.py", scripts_dir / "setup_wizard.py")
|
||||
return checkout
|
||||
|
||||
|
||||
def _run_wizard(checkout: Path, *args: str) -> subprocess.CompletedProcess[str]:
|
||||
env = {name: value for name, value in os.environ.items() if not name.startswith("DEER_FLOW_") and name not in {"PYTHONPATH", "PYTHONHOME"}}
|
||||
return subprocess.run(
|
||||
[sys.executable, str(checkout / "scripts" / "setup_wizard.py"), *args],
|
||||
cwd=checkout,
|
||||
env=env,
|
||||
stdin=subprocess.DEVNULL,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
check=False,
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
|
||||
def test_help_prints_usage_without_starting_the_wizard(wizard_checkout: Path):
|
||||
result = _run_wizard(wizard_checkout, "--help")
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert "usage:" in result.stdout
|
||||
assert "Welcome to DeerFlow Setup!" not in result.stdout
|
||||
assert "Non-interactive environment detected" not in result.stdout
|
||||
|
||||
|
||||
def test_unknown_flag_exits_2_with_usage_error(wizard_checkout: Path):
|
||||
result = _run_wizard(wizard_checkout, "--verbos")
|
||||
|
||||
assert result.returncode == 2
|
||||
assert "unrecognized arguments: --verbos" in result.stderr
|
||||
assert "Welcome to DeerFlow Setup!" not in result.stdout
|
||||
|
||||
|
||||
def test_no_arguments_still_enters_the_wizard(wizard_checkout: Path):
|
||||
result = _run_wizard(wizard_checkout)
|
||||
|
||||
# The subprocess has no tty, so the wizard reports that instead of prompting.
|
||||
assert result.returncode == 1, result.stderr
|
||||
assert "Non-interactive environment detected" in result.stdout
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
@ -194,4 +195,15 @@ def main() -> int:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Parsed here rather than inside main(): the dependency-check tests call
|
||||
# main() in-process under pytest, whose argv must not reach argparse.
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="python scripts/check.py",
|
||||
description=(
|
||||
"DeerFlow dependency check (make check): probes the host tools the "
|
||||
"local development setup needs and prints install hints for the "
|
||||
"missing ones. Takes no arguments."
|
||||
),
|
||||
)
|
||||
parser.parse_args()
|
||||
sys.exit(main())
|
||||
|
||||
@ -7,6 +7,7 @@ Usage:
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
@ -174,4 +175,17 @@ def main() -> int:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Parsed here rather than inside main(): the wizard tests call main()
|
||||
# in-process, whose argv must not reach argparse. It also keeps --help from
|
||||
# entering the prompts, which on an existing config.yaml leads to the
|
||||
# "reconfigure?" question and rewrites the operator's configuration.
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="python scripts/setup_wizard.py",
|
||||
description=(
|
||||
"DeerFlow interactive setup wizard (make setup): prompts for the LLM "
|
||||
"provider, web search/fetch and sandbox settings, then writes "
|
||||
"config.yaml and .env. Takes no arguments."
|
||||
),
|
||||
)
|
||||
parser.parse_args()
|
||||
sys.exit(main())
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user