210 lines
6.4 KiB
Python
Executable File
210 lines
6.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
DEFAULT_SMOKE_PACKS = [
|
|
"root",
|
|
"patch1",
|
|
"patch2",
|
|
"season3_eu",
|
|
"metin2_patch_snow",
|
|
"metin2_patch_snow_dungeon",
|
|
"metin2_patch_etc_costume1",
|
|
"metin2_patch_pet1",
|
|
"metin2_patch_pet2",
|
|
"metin2_patch_ramadan_costume",
|
|
"metin2_patch_flame",
|
|
"metin2_patch_flame_dungeon",
|
|
"locale",
|
|
"uiscript",
|
|
"uiloading",
|
|
"ETC",
|
|
"item",
|
|
"effect",
|
|
"icon",
|
|
"property",
|
|
]
|
|
|
|
|
|
def env_path(name: str) -> Path | None:
|
|
value = os.environ.get(name, "").strip()
|
|
return Path(value).expanduser().resolve() if value else None
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(
|
|
description="Run the Linux headless E2E, runtime gate, and optional Wine .m2p smoke on a self-hosted runner."
|
|
)
|
|
parser.add_argument(
|
|
"--runtime-root",
|
|
type=Path,
|
|
default=env_path("M2_RUNTIME_ROOT"),
|
|
help="Client runtime root containing assets/, config/, and pack/. Defaults to $M2_RUNTIME_ROOT.",
|
|
)
|
|
parser.add_argument(
|
|
"--client-repo",
|
|
type=Path,
|
|
default=env_path("M2_CLIENT_REPO"),
|
|
help="Path to m2dev-client-src. Defaults to $M2_CLIENT_REPO.",
|
|
)
|
|
parser.add_argument(
|
|
"--master-key",
|
|
type=Path,
|
|
default=env_path("M2_MASTER_KEY_PATH"),
|
|
help="Path to runtime master key file. Defaults to $M2_MASTER_KEY_PATH.",
|
|
)
|
|
parser.add_argument(
|
|
"--key-id",
|
|
type=str,
|
|
default=os.environ.get("M2_KEY_ID", "1"),
|
|
help="Runtime key id for Wine smoke. Defaults to $M2_KEY_ID or 1.",
|
|
)
|
|
parser.add_argument(
|
|
"--timeout",
|
|
type=int,
|
|
default=int(os.environ.get("M2_TIMEOUT", "20")),
|
|
help="Wine smoke timeout in seconds. Defaults to $M2_TIMEOUT or 20.",
|
|
)
|
|
parser.add_argument(
|
|
"--skip-headless-e2e",
|
|
action="store_true",
|
|
help="Skip scripts/headless_e2e.py.",
|
|
)
|
|
parser.add_argument(
|
|
"--skip-runtime-gate",
|
|
action="store_true",
|
|
help="Skip scripts/validate_runtime_gate.py.",
|
|
)
|
|
parser.add_argument(
|
|
"--skip-runtime-smoke",
|
|
action="store_true",
|
|
help="Skip scripts/runtime_smoke_wine.py.",
|
|
)
|
|
parser.add_argument(
|
|
"--smoke-pack",
|
|
dest="smoke_packs",
|
|
action="append",
|
|
default=[],
|
|
help="Pack basename for Wine smoke. Repeatable. Defaults to a known startup-safe set or $M2_RUNTIME_SMOKE_PACKS.",
|
|
)
|
|
parser.add_argument(
|
|
"--json",
|
|
action="store_true",
|
|
help="Emit machine-readable JSON output.",
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
def resolve_smoke_packs(cli_values: list[str]) -> list[str]:
|
|
if cli_values:
|
|
return cli_values
|
|
env_value = os.environ.get("M2_RUNTIME_SMOKE_PACKS", "").strip()
|
|
if env_value:
|
|
return [part.strip() for part in env_value.split(",") if part.strip()]
|
|
return list(DEFAULT_SMOKE_PACKS)
|
|
|
|
|
|
def run_json(cmd: list[str]) -> dict:
|
|
completed = subprocess.run(
|
|
cmd,
|
|
cwd=REPO_ROOT,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True,
|
|
)
|
|
if completed.returncode != 0:
|
|
detail = completed.stderr.strip() or completed.stdout.strip() or f"exit code {completed.returncode}"
|
|
raise RuntimeError(f"command failed: {' '.join(cmd)}\n{detail}")
|
|
stdout = completed.stdout.strip()
|
|
return json.loads(stdout) if stdout else {"ok": True}
|
|
|
|
|
|
def main() -> int:
|
|
args = parse_args()
|
|
smoke_packs = resolve_smoke_packs(args.smoke_packs)
|
|
summary: dict[str, object] = {
|
|
"ok": True,
|
|
"runtime_root": str(args.runtime_root) if args.runtime_root else None,
|
|
"client_repo": str(args.client_repo) if args.client_repo else None,
|
|
"smoke_packs": smoke_packs,
|
|
"steps": {},
|
|
}
|
|
|
|
try:
|
|
if not args.skip_headless_e2e:
|
|
summary["steps"]["headless_e2e"] = run_json([sys.executable, "scripts/headless_e2e.py"])
|
|
else:
|
|
summary["steps"]["headless_e2e"] = {"ok": True, "skipped": True}
|
|
|
|
if not args.skip_runtime_gate:
|
|
if not args.runtime_root:
|
|
raise ValueError("runtime root is required for runtime gate")
|
|
summary["steps"]["runtime_gate"] = run_json(
|
|
[
|
|
sys.executable,
|
|
"scripts/validate_runtime_gate.py",
|
|
"--runtime-root",
|
|
str(args.runtime_root),
|
|
"--json",
|
|
]
|
|
)
|
|
else:
|
|
summary["steps"]["runtime_gate"] = {"ok": True, "skipped": True}
|
|
|
|
if not args.skip_runtime_smoke:
|
|
if not args.runtime_root:
|
|
raise ValueError("runtime root is required for runtime smoke")
|
|
if not args.client_repo:
|
|
raise ValueError("client repo is required for runtime smoke")
|
|
if not args.master_key:
|
|
raise ValueError("master key is required for runtime smoke")
|
|
|
|
cmd = [
|
|
sys.executable,
|
|
"scripts/runtime_smoke_wine.py",
|
|
"--runtime-root",
|
|
str(args.runtime_root),
|
|
"--client-repo",
|
|
str(args.client_repo),
|
|
"--master-key",
|
|
str(args.master_key),
|
|
"--key-id",
|
|
args.key_id,
|
|
"--timeout",
|
|
str(args.timeout),
|
|
"--json",
|
|
]
|
|
for pack in smoke_packs:
|
|
cmd.extend(["--pack", pack])
|
|
summary["steps"]["runtime_smoke"] = run_json(cmd)
|
|
else:
|
|
summary["steps"]["runtime_smoke"] = {"ok": True, "skipped": True}
|
|
|
|
summary["ok"] = all(bool(step.get("ok", False)) for step in summary["steps"].values())
|
|
except Exception as exc:
|
|
summary["ok"] = False
|
|
summary["error"] = str(exc)
|
|
|
|
if args.json:
|
|
print(json.dumps(summary, indent=2))
|
|
else:
|
|
print(f"ok={summary['ok']}")
|
|
for name, result in summary["steps"].items():
|
|
print(f"{name}: ok={result.get('ok')} skipped={result.get('skipped', False)}")
|
|
if "error" in summary:
|
|
print(f"error: {summary['error']}")
|
|
|
|
return 0 if summary["ok"] else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|