explainer-env / tests /test_client_server.py
kgdrathan's picture
Upload folder using huggingface_hub
43f41de verified
"""Integration test: start server, connect client, run explore→generate.
Usage:
uv run python tests/test_client_server.py # auto-starts server
uv run python tests/test_client_server.py --url http://localhost:8000
"""
import argparse
import subprocess
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from client import ExplainerEnv
from models import ExplainerAction
def wait_for_server(url: str, timeout: int = 15):
import urllib.request
deadline = time.time() + timeout
while time.time() < deadline:
try:
urllib.request.urlopen(f"{url}/health", timeout=2)
return True
except Exception:
time.sleep(0.5)
return False
def run_tests(base_url: str):
client = ExplainerEnv(base_url=base_url)
with client.sync() as sc:
# --- reset ---
result = sc.reset()
obs = result.observation
assert obs.topic, "reset should return a topic"
assert obs.phase == "explore"
assert obs.explore_steps_left == 3
print(f" reset: topic={obs.topic!r}, phase={obs.phase}")
# --- explore ---
action = ExplainerAction(
action_type="explore",
tool="search_wikipedia",
query=obs.topic,
intent="overview",
)
result = sc.step(action)
assert not result.done
assert result.observation.explore_steps_left == 2
print(f" explore: reward={result.reward:.3f}, steps_left={result.observation.explore_steps_left}")
# --- generate ---
action = ExplainerAction(
action_type="generate",
format="marimo",
code="import marimo as mo\napp = mo.App()\n@app.cell\ndef _():\n mo.md('hi')\n return\n",
)
result = sc.step(action)
if not result.done:
result = sc.step(ExplainerAction(
action_type="repair",
format="marimo",
code="import marimo as mo\napp = mo.App()\n@app.cell\ndef _():\n mo.md('hi')\n return\n",
))
assert result.done
assert isinstance(result.reward, (int, float))
print(f" generate: reward={result.reward:.3f}, done={result.done}")
# --- second episode ---
result2 = sc.reset()
assert result2.observation.topic
print(f" reset2: topic={result2.observation.topic!r}")
print("PASS: test_client_server (4/4)")
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--url", default=None)
args = parser.parse_args()
if args.url:
run_tests(args.url)
else:
port = "8010"
proc = subprocess.Popen(
["uv", "run", "uvicorn", "server.app:app", "--host", "0.0.0.0", "--port", port],
cwd=str(Path(__file__).resolve().parents[1]),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
try:
url = f"http://localhost:{port}"
if not wait_for_server(url):
stderr = proc.stderr.read().decode() if proc.stderr else ""
print(f"FAIL: server did not start\n{stderr}", file=sys.stderr)
sys.exit(1)
run_tests(url)
finally:
proc.terminate()
proc.wait(timeout=5)
if __name__ == "__main__":
main()