139 lines
6.2 KiB
Python
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())
|