Files
NeitzelFXDesigner/scripts/check-ide-host.py
T

87 lines
4.4 KiB
Python

#!/usr/bin/env python3
"""Run the disposable companion checks in real isolated IntelliJ windows (macOS)."""
import argparse
import hashlib
import json
import os
from pathlib import Path
import re
import shutil
import signal
import subprocess
import sys
import time
import tempfile
import zipfile
from native_crashes import NativeCrashGuard, descendant_workers, await_worker_exit
ROOT = Path(__file__).resolve().parent.parent
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("--java-home", type=Path, required=True)
p.add_argument("--target-java-home", type=Path, required=True)
p.add_argument("--idea-home", type=Path, required=True)
p.add_argument("--mode", choices=["all", "classpath", "modulepath"], default="all")
p.add_argument("--repeat", type=int, default=1, help="Consecutive independent runs per mode")
args = p.parse_args()
if args.repeat < 1:
p.error("--repeat must be positive")
output = ROOT / "target/ide-workflow"
output.mkdir(parents=True, exist_ok=True)
batch = Path(tempfile.mkdtemp(prefix="runs-", dir=output))
print("Host evidence: " + str(batch), flush=True)
modes = ("classpath", "modulepath") if args.mode == "all" else (args.mode,)
for run_number, mode in ((n, mode) for n in range(1, args.repeat + 1) for mode in modes):
crashes = NativeCrashGuard(ROOT)
launch = subprocess.run([sys.executable, ROOT / "scripts/run-fixture-host.py", "intellij",
"--check", "--mode", mode, "--java-home", args.java_home,
"--target-java-home", args.target_java_home, "--idea-home", args.idea_home],
cwd=ROOT, text=True, capture_output=True, check=True, timeout=60)
evidence = batch / f"{mode}-run-{run_number}"
evidence.mkdir(exist_ok=True)
(output / (mode + "-launch.log")).write_text(launch.stdout + launch.stderr)
(evidence / "launch.log").write_text(launch.stdout + launch.stderr)
base = Path(re.search(r"Disposable host: (.+)", launch.stdout)[1])
pid = int(re.search(r"Host PID: (\d+)", launch.stdout)[1])
try:
until = time.monotonic() + 240
while time.monotonic() < until:
if (base / "FAILURE.txt").exists():
raise RuntimeError((base / "FAILURE.txt").read_text())
if (base / "SUCCESS.txt").exists():
break
time.sleep(.25)
else:
raise RuntimeError("IDE qualification timed out: " + str(base))
natives = subprocess.run([args.java_home / "bin/jcmd", str(pid), "VM.dynlibs"],
capture_output=True, text=True, check=True, timeout=20).stdout
(output / (mode + "-natives.txt")).write_text(natives)
paths = sorted(set(re.findall(r"(/[^\r\n]*?libglass\.dylib)", natives)))
expected = {}
for artifact in (Path.home() / ".m2/repository/org/openjfx").glob("*/26/*-mac*.jar"):
with zipfile.ZipFile(artifact) as jar:
for name in jar.namelist():
if name.endswith(".dylib"):
expected.setdefault(Path(name).name, set()).add(hashlib.sha256(jar.read(name)).hexdigest())
actual = {path: hashlib.sha256(Path(path).read_bytes()).hexdigest() for path in paths}
assert len(actual) == 1 and next(iter(actual.values())) in expected["libglass.dylib"], actual
all_paths = sorted(set(re.findall(r"(/[^\r\n]*?/\.openjfx/[^\r\n]*?\.dylib)", natives)))
all_native = {path: hashlib.sha256(Path(path).read_bytes()).hexdigest() for path in all_paths}
assert all(digest in expected.get(Path(path).name, set()) for path, digest in all_native.items())
proof = {"mode": mode, "host": str(base), "glass": actual, "openjfx": all_native,
"result": (base / "SUCCESS.txt").read_text()}
(output / (mode + ".json")).write_text(json.dumps(proof, indent=2) + "\n")
shutil.copyfile(base / "intellij.png", output / (mode + ".png"))
(evidence / "proof.json").write_text(json.dumps(proof, indent=2) + "\n")
finally:
for name in ("SUCCESS.txt", "FAILURE.txt", "ui-state.txt", "launcher.log"):
if (base / name).exists():
shutil.copyfile(base / name, evidence / name)
workers = descendant_workers(pid)
try:
os.kill(pid, signal.SIGTERM)
except ProcessLookupError:
pass
await_worker_exit(workers)
crashes.check()
print("PASS registered IntelliJ " + mode + f" run {run_number}: editor, exclusive JavaFX 26 natives and worker shutdown", flush=True)