a7d6d88f6f
CI / changes (push) Has been cancelled
CI / cd libs/checkpoint (push) Has been cancelled
CI / cd libs/checkpoint-conformance (push) Has been cancelled
CI / cd libs/checkpoint-postgres (push) Has been cancelled
CI / cd libs/checkpoint-sqlite (push) Has been cancelled
CI / cd libs/cli (push) Has been cancelled
CI / cd libs/prebuilt (push) Has been cancelled
CI / cd libs/sdk-py (push) Has been cancelled
CI / cd libs/langgraph (push) Has been cancelled
CI / Check SDK methods matching (push) Has been cancelled
CI / Check CLI schema hasn't changed #3.13 (push) Has been cancelled
CI / CLI integration test (push) Has been cancelled
CI / sdk-py integration test (push) Has been cancelled
CI / CI Success (push) Has been cancelled
baseline / benchmark (push) Has been cancelled
Deploy Redirects to GitHub Pages / deploy (push) Has been cancelled
178 lines
6.1 KiB
Python
178 lines
6.1 KiB
Python
import logging
|
|
import pathlib
|
|
import sys
|
|
import time
|
|
from urllib import error, request
|
|
|
|
import langgraph_cli
|
|
import langgraph_cli.config
|
|
import langgraph_cli.docker
|
|
from langgraph_cli.cli import prepare_args_and_stdin
|
|
from langgraph_cli.constants import DEFAULT_PORT
|
|
from langgraph_cli.exec import Runner, subp_exec
|
|
from langgraph_cli.progress import Progress
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
|
|
def test(config: pathlib.Path, port: int, tag: str, verbose: bool):
|
|
"""Spin up API with Postgres/Redis via docker compose and wait until ready."""
|
|
logger.info("Starting test...")
|
|
with Runner() as runner, Progress(message="Pulling...") as set:
|
|
# Detect docker/compose capabilities
|
|
capabilities = langgraph_cli.docker.check_capabilities(runner)
|
|
|
|
# Validate config and prepare compose stdin/args using built image
|
|
config_json = langgraph_cli.config.validate_config_file(config)
|
|
args, stdin = prepare_args_and_stdin(
|
|
capabilities=capabilities,
|
|
config_path=config,
|
|
config=config_json,
|
|
docker_compose=None,
|
|
port=port,
|
|
watch=False,
|
|
debugger_port=None,
|
|
debugger_base_url=f"http://127.0.0.1:{port}",
|
|
postgres_uri=None,
|
|
api_version=None,
|
|
image=tag,
|
|
base_image=None,
|
|
)
|
|
|
|
# Compose up with wait (implies detach), similar to `langgraph up --wait`
|
|
args_up = [*args, "up", "--remove-orphans", "--wait"]
|
|
|
|
compose_cmd = ["docker", "compose"]
|
|
if capabilities.compose_type == "standalone":
|
|
compose_cmd = ["docker-compose"]
|
|
|
|
set("Starting...")
|
|
try:
|
|
runner.run(
|
|
subp_exec(
|
|
*compose_cmd,
|
|
*args_up,
|
|
input=stdin,
|
|
verbose=verbose,
|
|
)
|
|
)
|
|
except Exception as e: # noqa: BLE001
|
|
# On failure, show diagnostics then ensure clean teardown
|
|
sys.stderr.write(f"docker compose up failed: {e}\n")
|
|
try:
|
|
sys.stderr.write("\n== docker compose ps ==\n")
|
|
runner.run(
|
|
subp_exec(*compose_cmd, *args, "ps", input=stdin, verbose=True)
|
|
)
|
|
except Exception:
|
|
pass
|
|
try:
|
|
sys.stderr.write("\n== docker compose logs (api) ==\n")
|
|
runner.run(
|
|
subp_exec(
|
|
*compose_cmd,
|
|
*args,
|
|
"logs",
|
|
"langgraph-api",
|
|
input=stdin,
|
|
verbose=True,
|
|
)
|
|
)
|
|
except Exception:
|
|
pass
|
|
finally:
|
|
try:
|
|
runner.run(
|
|
subp_exec(
|
|
*compose_cmd,
|
|
*args,
|
|
"down",
|
|
"-v",
|
|
"--remove-orphans",
|
|
input=stdin,
|
|
verbose=False,
|
|
)
|
|
)
|
|
finally:
|
|
raise
|
|
|
|
set("")
|
|
base_url = f"http://localhost:{port}"
|
|
ok_url = f"{base_url}/ok"
|
|
logger.info(f"Waiting for {ok_url} to respond with 200...")
|
|
deadline = time.time() + 30
|
|
last_err: Exception | None = None
|
|
while time.time() < deadline:
|
|
try:
|
|
with request.urlopen(ok_url, timeout=2) as resp:
|
|
if resp.status == 200:
|
|
sys.stdout.write(
|
|
f"""Ready!\n- API: {base_url}\n- /ok: 200 OK\n"""
|
|
)
|
|
sys.stdout.flush()
|
|
break
|
|
else:
|
|
last_err = RuntimeError(f"Unexpected status: {resp.status}")
|
|
logger.error(f"Unexpected status: {resp.status}")
|
|
except error.URLError as e:
|
|
logger.error(f"URLError: {e}")
|
|
last_err = e
|
|
except Exception as e: # noqa: BLE001
|
|
logger.error(f"Exception: {e}")
|
|
last_err = e
|
|
time.sleep(0.5)
|
|
else:
|
|
logger.error("Timeout waiting for /ok to return 200")
|
|
# Bring stack down before raising
|
|
args_down = [*args, "down", "-v", "--remove-orphans"]
|
|
try:
|
|
runner.run(
|
|
subp_exec(
|
|
*compose_cmd,
|
|
*args_down,
|
|
input=stdin,
|
|
verbose=verbose,
|
|
)
|
|
)
|
|
finally:
|
|
raise SystemExit(
|
|
f"/ok did not return 202 within timeout. Last error: {last_err}"
|
|
)
|
|
|
|
# Clean up: bring compose stack down to free ports for next test
|
|
logger.info("Test succeeded. Bringing down compose stack...")
|
|
try:
|
|
args_down = [*args, "down", "-v", "--remove-orphans"]
|
|
runner.run(
|
|
subp_exec(
|
|
*compose_cmd,
|
|
*args_down,
|
|
input=stdin,
|
|
verbose=verbose,
|
|
)
|
|
)
|
|
logger.info("Compose stack down. Finishing...")
|
|
except Exception:
|
|
logger.exception("Failed to bring down compose stack")
|
|
pass
|
|
|
|
logger.info("Test finished")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("-t", "--tag", type=str)
|
|
parser.add_argument("-c", "--config", type=str, default="./langgraph.json")
|
|
parser.add_argument("-p", "--port", type=int, default=DEFAULT_PORT)
|
|
args = parser.parse_args()
|
|
try:
|
|
test(pathlib.Path(args.config), args.port, args.tag, verbose=True)
|
|
except BaseException:
|
|
logger.exception("Test failed")
|
|
raise
|
|
|
|
logger.info("Test execution finished")
|