87 lines
4.4 KiB
Python
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)
|