#!/usr/bin/env python3 """Remote CI end-to-end smoke test, no external database required.""" import json import os from pathlib import Path import signal import socket import subprocess import tempfile import time import urllib.request ROOT = Path(__file__).resolve().parents[1] def run(*args, env): return subprocess.run(args, cwd=ROOT, env=env, check=True, text=True, capture_output=True).stdout.strip() def main(): env = os.environ.copy() env.pop("DATABASE_URL", None) run("cargo", "build", "--workspace", "--locked", env=env) # Cargo may use a configured CARGO_TARGET_DIR; discover the actual binaries. metadata = json.loads(run("cargo", "metadata", "--no-deps", "--format-version", "1", env=env)) binary_dir = Path(metadata["target_directory"]) / "debug" cli = str(binary_dir / "aster") server = str(binary_dir / "aster-server") with tempfile.TemporaryDirectory(prefix="aster-smoke-") as directory: env["SQLITE_PATH"] = str(Path(directory) / "nested" / "aster.db") # This must succeed even when an explicit database URL is invalid. isolated_env = {**env, "DATABASE_URL": "invalid-database-url"} bootstrap = run(cli, "bootstrap", env=isolated_env) assert "runtime=EndorObject type_system=disabled" in bootstrap, bootstrap assert "objects_distinct=true heap_objects=2" in bootstrap, bootstrap assert "released heap_objects=0" in bootstrap, bootstrap assert not Path(env["SQLITE_PATH"]).exists(), "bootstrap must not open storage" assert "sqlite" in run(cli, "metadata", "put", "smoke", "持久化成功", env=env) assert run(cli, "metadata", "get", "smoke", env=env) == "持久化成功" failed = subprocess.run([cli, "metadata", "get", "smoke"], cwd=ROOT, env=isolated_env, capture_output=True) assert failed.returncode != 0, "invalid explicit DB config must fail" with socket.socket() as sock: sock.bind(("127.0.0.1", 0)) port = sock.getsockname()[1] env["ASTER_LISTEN"] = f"127.0.0.1:{port}" with tempfile.TemporaryFile(mode="w+") as log: process = subprocess.Popen([server], cwd=ROOT, env=env, stdout=log, stderr=log) try: deadline = time.monotonic() + 15 while True: try: with urllib.request.urlopen(f"http://127.0.0.1:{port}/health", timeout=1) as response: health = json.load(response) break except (OSError, ValueError): if process.poll() is not None or time.monotonic() >= deadline: log.seek(0) raise AssertionError(f"server did not become healthy: {log.read()}") time.sleep(0.1) assert health == {"status": "ok", "storage": "sqlite"}, health for _ in range(2): with urllib.request.urlopen(f"http://127.0.0.1:{port}/bootstrap", timeout=2) as response: info = json.load(response) assert info == {"object_model": "EndorObject", "type_system": False, "heap_objects": 0}, info finally: process.send_signal(signal.SIGTERM) try: process.wait(timeout=10) except subprocess.TimeoutExpired: process.kill() process.wait() raise AssertionError("server did not shut down gracefully") assert process.returncode == 0, process.returncode assert run(cli, "metadata", "get", "host.version", env=env) == "0.1.0" print("PASS: bootstrap isolation; SQLite persistence; config failure; HTTP health/bootstrap; SIGTERM shutdown") if __name__ == "__main__": main()