162 lines
5.1 KiB
Python
162 lines
5.1 KiB
Python
"""Scripted React TUI end-to-end checks using the real CLI entrypoint."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import pexpect
|
|
|
|
from openharness.config.settings import load_settings
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def _spawn_oh(*, env: dict[str, str] | None = None) -> pexpect.spawn:
|
|
args = ["run", "oh"]
|
|
child = pexpect.spawn(
|
|
"uv",
|
|
args,
|
|
cwd=str(ROOT),
|
|
env=env or os.environ,
|
|
encoding="utf-8",
|
|
timeout=180,
|
|
)
|
|
child.delaybeforesend = 0.1
|
|
if os.environ.get("OPENHARNESS_E2E_DEBUG") == "1":
|
|
child.logfile_read = sys.stdout
|
|
return child
|
|
|
|
|
|
def _submit(child: pexpect.spawn, text: str) -> None:
|
|
for character in text:
|
|
child.send(character)
|
|
time.sleep(0.02)
|
|
time.sleep(0.2)
|
|
child.send("\r")
|
|
time.sleep(0.4)
|
|
|
|
|
|
def _isolated_env(permission_mode: str = "full_auto") -> tuple[tempfile.TemporaryDirectory[str], dict[str, str]]:
|
|
settings = load_settings()
|
|
temp_dir = tempfile.TemporaryDirectory(prefix="openharness-react-tui-")
|
|
config_dir = Path(temp_dir.name) / "config"
|
|
data_dir = Path(temp_dir.name) / "data"
|
|
config_dir.mkdir(parents=True, exist_ok=True)
|
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
payload = settings.model_dump(mode="json")
|
|
payload["permission"]["mode"] = permission_mode
|
|
(config_dir / "settings.json").write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
|
|
env = os.environ.copy()
|
|
env["OPENHARNESS_CONFIG_DIR"] = str(config_dir)
|
|
env["OPENHARNESS_DATA_DIR"] = str(data_dir)
|
|
env["OPENHARNESS_FRONTEND_RAW_RETURN"] = "1"
|
|
return temp_dir, env
|
|
|
|
|
|
def _run_permission_file_io() -> None:
|
|
path = ROOT / "react_tui_smoke.txt"
|
|
if path.exists():
|
|
path.unlink()
|
|
temp_dir, env = _isolated_env()
|
|
child = _spawn_oh(env=env)
|
|
try:
|
|
print("[react_tui_permission_file_io] waiting for app shell")
|
|
child.expect("OpenHarness")
|
|
child.expect("model: kimi-k2.5")
|
|
_submit(
|
|
child,
|
|
"You are running a React TUI end-to-end test. "
|
|
"Use write_file to create react_tui_smoke.txt with exact content REACT_TUI_OK, "
|
|
"then use read_file to verify it, then reply with exactly FINAL_OK_REACT_TUI.",
|
|
)
|
|
print("[react_tui_permission_file_io] waiting for final marker")
|
|
child.expect(r"(?s)assistant>.*FINAL_OK_REACT_TUI")
|
|
finally:
|
|
child.sendcontrol("c")
|
|
child.close(force=True)
|
|
temp_dir.cleanup()
|
|
assert path.read_text(encoding="utf-8") == "REACT_TUI_OK"
|
|
print("[react_tui_permission_file_io] PASS")
|
|
|
|
|
|
def _run_question_flow() -> None:
|
|
path = ROOT / "react_tui_question.txt"
|
|
if path.exists():
|
|
path.unlink()
|
|
temp_dir, env = _isolated_env()
|
|
child = _spawn_oh(env=env)
|
|
try:
|
|
child.expect("OpenHarness")
|
|
child.expect("model: kimi-k2.5")
|
|
_submit(
|
|
child,
|
|
"You are running a React TUI question flow test. "
|
|
"Use ask_user_question to ask for a color. "
|
|
"After the answer arrives, use write_file to create react_tui_question.txt with that exact answer, "
|
|
"then use read_file to verify it, then reply with exactly FINAL_OK_REACT_TUI_QUESTION.",
|
|
)
|
|
print("[react_tui_question_flow] waiting for question modal")
|
|
child.expect("Question")
|
|
child.expect("color")
|
|
_submit(child, "teal")
|
|
print("[react_tui_question_flow] waiting for final marker")
|
|
child.expect(r"(?s)assistant>.*FINAL_OK_REACT_TUI_QUESTION")
|
|
finally:
|
|
child.sendcontrol("c")
|
|
child.close(force=True)
|
|
temp_dir.cleanup()
|
|
assert path.read_text(encoding="utf-8") == "teal"
|
|
print("[react_tui_question_flow] PASS")
|
|
|
|
|
|
def _run_command_flow() -> None:
|
|
temp_dir, env = _isolated_env()
|
|
env["OPENHARNESS_FRONTEND_SCRIPT"] = json.dumps(
|
|
[
|
|
"/plan",
|
|
"Reply with exactly FINAL_OK_REACT_TUI_COMMANDS.",
|
|
]
|
|
)
|
|
child = _spawn_oh(env=env)
|
|
try:
|
|
print("[react_tui_command_flow] waiting for app shell")
|
|
child.expect("OpenHarness")
|
|
child.expect("model: kimi-k2.5")
|
|
print("[react_tui_command_flow] waiting for plan mode indicator")
|
|
child.expect("PLAN MODE")
|
|
print("[react_tui_command_flow] waiting for final marker")
|
|
child.expect(r"(?s)assistant>.*FINAL_OK_REACT_TUI_COMMANDS")
|
|
finally:
|
|
child.sendcontrol("c")
|
|
child.close(force=True)
|
|
temp_dir.cleanup()
|
|
print("[react_tui_command_flow] PASS")
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Run scripted React TUI E2E scenarios")
|
|
parser.add_argument(
|
|
"--scenario",
|
|
choices=["all", "permission_file_io", "question_flow", "command_flow"],
|
|
default="all",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
if args.scenario in {"all", "permission_file_io"}:
|
|
_run_permission_file_io()
|
|
if args.scenario in {"all", "command_flow"}:
|
|
_run_command_flow()
|
|
if args.scenario == "question_flow":
|
|
_run_question_flow()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|