Files
Adminive/scripts/verify.py
T
2026-08-08 20:32:33 +08:00

139 lines
6.2 KiB
Python

from __future__ import annotations
import argparse
import os
from pathlib import Path
import shutil
import subprocess
import sys
import time
import urllib.request
ROOT = Path(__file__).resolve().parents[1]
FRONTEND = ROOT / "frontend"
VERIFICATION = ROOT / "verification"
PORT = 9999
PARALLEL = os.environ.get("ADMINIVE_VERIFY_JOBS", "1")
BASE_URL = f"http://127.0.0.1:{PORT}"
SANITIZER_TARGETS = ["Adminive_Core_Adapter_Test", "Adminive_Advanced_Adapter_Test", "Adminive_Safety_Test", "Adminive_Managed_Test", "Adminive_View_Schema_Test", "Adminive_Drogon_Adapter_Test", "Adminive_Httplib_Adapter_Test"]
def run(command: list[str], cwd: Path = ROOT, env: dict[str, str] | None = None) -> None:
print("+", " ".join(command), flush=True)
subprocess.run(command, cwd=cwd, env=env, check=True)
def configure(build_dir: Path, configuration: str, extra: list[str] | None = None) -> None:
command = ["cmake", "-S", str(ROOT), "-B", str(build_dir), f"-DCMAKE_BUILD_TYPE={configuration}", "-DBUILD_TESTING=ON"]
if extra:
command.extend(extra)
run(command)
def build(build_dir: Path, configuration: str, target: str | None = None) -> None:
command = ["cmake", "--build", str(build_dir), "--config", configuration, "--parallel", PARALLEL]
if target:
command.extend(["--target", target])
run(command)
def ctest(build_dir: Path, configuration: str, label: str | None = None) -> None:
command = ["ctest", "--test-dir", str(build_dir), "-C", configuration, "--output-on-failure"]
if label:
command.extend(["-L", label])
run(command)
def ctest_regex(build_dir: Path, configuration: str, names: list[str]) -> None:
command = ["ctest", "--test-dir", str(build_dir), "-C", configuration, "--output-on-failure", "-R", "^(" + "|".join(names) + ")$"]
run(command)
def server_executable(build_dir: Path, configuration: str) -> Path:
name = "Adminive_Server.exe" if os.name == "nt" else "Adminive_Server"
candidates = [build_dir / "backend" / "service" / configuration / name, build_dir / "backend" / "service" / name]
for candidate in candidates:
if candidate.is_file():
return candidate
raise RuntimeError(f"Adminive_Server not found under {build_dir}")
def wait_for_server(process: subprocess.Popen[bytes]) -> None:
deadline = time.monotonic() + 20
while time.monotonic() < deadline:
if process.poll() is not None:
raise RuntimeError(f"Adminive_Server exited with {process.returncode}")
try:
with urllib.request.urlopen(f"{BASE_URL}/admin/gallery/docs", timeout=1) as response:
if response.status == 200:
return
except Exception:
time.sleep(0.2)
raise RuntimeError("Adminive_Server did not become ready")
def npm_command() -> str:
return "npm.cmd" if os.name == "nt" else "npm"
def prepare_frontend() -> None:
npm = npm_command()
run([npm, "ci"], FRONTEND)
run([npm, "run", "test:unit"], FRONTEND)
run([npm, "run", "build"], FRONTEND)
run([npm, "exec", "--", "playwright", "install", "chromium"], FRONTEND)
def run_e2e(build_dir: Path, configuration: str, edge: bool) -> None:
npm = npm_command()
build(build_dir, configuration, "Adminive_Server")
if not (ROOT / "frontend_dist" / "index.html").is_file():
run([npm, "run", "build"], FRONTEND)
executable = server_executable(build_dir, configuration)
process = subprocess.Popen([str(executable), str(PORT)], cwd=ROOT, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
try:
wait_for_server(process)
env = os.environ.copy()
env["ADMINIVE_E2E_BASE_URL"] = BASE_URL
script = "test:e2e:edge" if edge else "test:e2e"
run([npm, "run", script], FRONTEND, env)
finally:
if process.poll() is None:
process.terminate()
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
process.kill()
process.wait(timeout=5)
if process.stdout:
output = process.stdout.read().decode(errors="replace")
if output:
print(output, end="")
def full_verification() -> None:
if VERIFICATION.exists():
shutil.rmtree(VERIFICATION)
debug_dir = VERIFICATION / "debug"
release_dir = VERIFICATION / "release"
asan_dir = VERIFICATION / "asan-ubsan"
tsan_dir = VERIFICATION / "tsan"
configure(debug_dir, "Debug")
build(debug_dir, "Debug")
ctest(debug_dir, "Debug")
configure(release_dir, "Release")
build(release_dir, "Release")
ctest(release_dir, "Release")
if os.name == "nt":
configure(asan_dir, "Debug", ["-DADMINIVE_ENABLE_ASAN=ON"])
for target in SANITIZER_TARGETS:
build(asan_dir, "Debug", target)
ctest_regex(asan_dir, "Debug", SANITIZER_TARGETS)
print("SKIP: UBSan and TSan verification require Clang or GNU and are not reported as passed on Windows MSVC", flush=True)
else:
configure(asan_dir, "Debug", ["-DADMINIVE_ENABLE_ASAN=ON", "-DADMINIVE_ENABLE_UBSAN=ON"])
for target in SANITIZER_TARGETS:
build(asan_dir, "Debug", target)
ctest_regex(asan_dir, "Debug", SANITIZER_TARGETS)
configure(tsan_dir, "Debug", ["-DADMINIVE_ENABLE_TSAN=ON"])
build(tsan_dir, "Debug", "Adminive_Managed_Test")
ctest(tsan_dir, "Debug", "concurrency")
prepare_frontend()
run_e2e(release_dir, "Release", False)
if os.name == "nt":
run_e2e(release_dir, "Release", True)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--e2e-only", action="store_true")
parser.add_argument("--edge", action="store_true")
parser.add_argument("--binary-dir", type=Path)
parser.add_argument("--configuration", default="Release")
args = parser.parse_args()
if args.edge and os.name != "nt":
raise RuntimeError("Edge E2E requires Windows with Microsoft Edge installed")
if args.e2e_only:
if args.binary_dir is None:
raise RuntimeError("--binary-dir is required with --e2e-only")
run_e2e(args.binary_dir.resolve(), args.configuration or "Release", args.edge)
return 0
full_verification()
return 0
if __name__ == "__main__":
sys.exit(main())