Connect minimal hosts to snapshots, target workers and shared Kit editing
This commit is contained in:
@@ -42,6 +42,7 @@ run([wrapper, "-B", "-ntp", "-pl", "designer-standalone", "org.apache.maven.plug
|
||||
smoke_classpath = os.pathsep.join([str(ROOT / "designer-standalone/target/test-classes"), str(ROOT / "designer-standalone/target/classes"), (ROOT / "designer-standalone/target/smoke-classpath.txt").read_text().strip()])
|
||||
run([Path(os.environ["JAVA_HOME"]) / "bin/java", "-cp", smoke_classpath, "de.neitzel.fxdesigner.build.BuildRefreshSmoke", ROOT, args.target_java_home])
|
||||
run([ROOT / "designer-intellij" / ("gradlew.bat" if os.name == "nt" else "gradlew"), "-p", "designer-intellij", "--no-daemon", "-PideaHome=" + str(idea), "clean", "test", "buildPlugin", "verifyPluginProjectConfiguration"])
|
||||
run([sys.executable, ROOT / "scripts/check-connected-workflow.py", "--idea-home", args.idea_home, "--target-java-home", args.target_java_home])
|
||||
with zipfile.ZipFile(next((ROOT / "designer-intellij/build/distributions").glob("*.zip"))) as plugin:
|
||||
names = plugin.namelist()
|
||||
assert any("javafx-graphics-26-mac" in n for n in names), "Missing host JavaFX implementation"
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Real snapshot/socket/overlay checks against both host document adapters."""
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import json
|
||||
import time
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--target-java-home", required=True)
|
||||
parser.add_argument("--idea-home", required=True)
|
||||
args = parser.parse_args()
|
||||
logs = ROOT / "target/connected-workflow"
|
||||
logs.mkdir(parents=True, exist_ok=True)
|
||||
classpath = os.pathsep.join([str(ROOT / "designer-standalone/target/test-classes"),
|
||||
str(ROOT / "designer-standalone/target/classes"),
|
||||
(ROOT / "designer-standalone/target/smoke-classpath.txt").read_text().strip()])
|
||||
|
||||
|
||||
def run(name, command):
|
||||
with (logs / (name + ".log")).open("w") as output:
|
||||
result = subprocess.run(list(map(str, command)), cwd=ROOT, stdout=output,
|
||||
stderr=subprocess.STDOUT, timeout=420)
|
||||
if result.returncode:
|
||||
raise RuntimeError((logs / (name + ".log")).read_text()[-16000:])
|
||||
print("PASS " + name, flush=True)
|
||||
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="designer-connected-") as temporary:
|
||||
for host in ("standalone", "intellij"):
|
||||
base = Path(temporary) / host
|
||||
for mode in ("classpath", "modulepath"):
|
||||
fixture = base / mode
|
||||
shutil.copytree(ROOT / "integration-fixtures/preview" / mode, fixture,
|
||||
ignore=shutil.ignore_patterns("target"))
|
||||
if host == "standalone":
|
||||
run(host + "-" + mode, [Path(os.environ["JAVA_HOME"]) / "bin/java", "-cp", classpath,
|
||||
"de.neitzel.fxdesigner.standalone.WorkflowSmoke", ROOT, args.target_java_home,
|
||||
fixture, mode])
|
||||
local = fixture / ".neitzel-fx-designer"
|
||||
(local / "runtimes.json").write_text(json.dumps({"schemaVersion":"1.0","runtimes":{"java21":{"build":args.target_java_home,"target":args.target_java_home}}}))
|
||||
tools = [ROOT / ("designer-" + name) / "target" / ("designer-" + name + "-0.1.0-SNAPSHOT.jar") for name in ("target-tools","contracts","preview-api")]
|
||||
(local / "host.properties").write_text("configuration=../neitzel-fx-designer.json\ncontext=fixture\nsettings=runtimes.json\nmaven="+str(ROOT/"mvnw")+"\ntools="+os.pathsep.join(map(str,tools))+"\n")
|
||||
run("native-kit-" + mode, [Path(os.environ["JAVA_HOME"]) / "bin/java", "--enable-native-access=ALL-UNNAMED", "-cp", classpath,
|
||||
"de.neitzel.fxdesigner.standalone.KitWorkflowSmoke", local / "host.properties",
|
||||
fixture / "app/src/main/resources/fixture/components/Person.fxml", logs / ("kit-" + mode + ".png")])
|
||||
if mode == "classpath":
|
||||
pidfile = base / "worker.pid"
|
||||
owner = subprocess.Popen([str(Path(os.environ["JAVA_HOME"]) / "bin/java"),
|
||||
"-cp", classpath, "de.neitzel.fxdesigner.standalone.WorkerOwnerProbe",
|
||||
str(ROOT), str(fixture), str(pidfile)], cwd=ROOT)
|
||||
try:
|
||||
until = time.monotonic() + 20
|
||||
while not pidfile.exists() and time.monotonic() < until:
|
||||
if owner.poll() is not None:
|
||||
raise RuntimeError("Owner probe exited before startup")
|
||||
time.sleep(.05)
|
||||
worker_pid = int(pidfile.read_text())
|
||||
owner.kill()
|
||||
owner.wait(timeout=5)
|
||||
until = time.monotonic() + 10
|
||||
while time.monotonic() < until:
|
||||
try:
|
||||
os.kill(worker_pid, 0)
|
||||
except ProcessLookupError:
|
||||
break
|
||||
time.sleep(.05)
|
||||
else:
|
||||
raise RuntimeError("Worker survived abrupt owner death")
|
||||
print("PASS abrupt owner death", flush=True)
|
||||
finally:
|
||||
if owner.poll() is None:
|
||||
owner.kill()
|
||||
owner.wait(timeout=5)
|
||||
|
||||
if host == "intellij":
|
||||
run(host, [ROOT / "designer-intellij/gradlew", "-p", "designer-intellij", "--no-daemon",
|
||||
"-PideaHome=" + args.idea_home, "-Ddesigner.workflow.fixtures=" + str(base),
|
||||
"-Ddesigner.repository=" + str(ROOT), "-Ddesigner.target.java=" + args.target_java_home,
|
||||
"test"])
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/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 zipfile
|
||||
|
||||
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)
|
||||
args = p.parse_args()
|
||||
output = ROOT / "target/ide-workflow"
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
for mode in ("classpath", "modulepath"):
|
||||
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)
|
||||
(output / (mode + "-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"))
|
||||
print("PASS registered IntelliJ " + mode + " editor and exclusive JavaFX 26 natives", flush=True)
|
||||
finally:
|
||||
try:
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Create a fresh editable development fixture and explicit local host setup."""
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--destination", type=Path, required=True)
|
||||
parser.add_argument("--target-java-home", type=Path, required=True)
|
||||
parser.add_argument("--mode", choices=["classpath", "modulepath"], default="classpath")
|
||||
args = parser.parse_args()
|
||||
destination = args.destination.resolve()
|
||||
shutil.copytree(ROOT / "integration-fixtures/preview" / args.mode, destination,
|
||||
ignore=shutil.ignore_patterns("target"))
|
||||
local = destination / ".neitzel-fx-designer"
|
||||
local.mkdir()
|
||||
(local / "runtimes.json").write_text(json.dumps({"schemaVersion":"1.0","runtimes":{"java21":{"build":str(args.target_java_home.resolve()),"target":str(args.target_java_home.resolve())}}}))
|
||||
tools = [ROOT / ("designer-" + name) / "target" / ("designer-" + name + "-0.1.0-SNAPSHOT.jar")
|
||||
for name in ("target-tools","contracts","preview-api")]
|
||||
# Properties separators in values do not need escaping; double backslashes do.
|
||||
def prop(value): return str(value).replace("\\", "\\\\")
|
||||
(local / "host.properties").write_text(
|
||||
"configuration=../neitzel-fx-designer.json\ncontext=fixture\nsettings=runtimes.json\n"
|
||||
+ "maven=" + prop(ROOT / "mvnw") + "\ntools=" + ":".join(map(prop, tools)) + "\n")
|
||||
print(local / "host.properties")
|
||||
print(destination / "app/src/main/resources/fixture/components/Person.fxml")
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Launch a disposable standalone or isolated IntelliJ fixture host (macOS development)."""
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import zipfile
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument("host", choices=["standalone","intellij"])
|
||||
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)
|
||||
p.add_argument("--check", action="store_true", help="Install the disposable IDE qualification companion")
|
||||
p.add_argument("--mode", choices=["classpath","modulepath"], default="classpath")
|
||||
args = p.parse_args()
|
||||
base = Path(tempfile.mkdtemp(prefix="neitzel-designer-host-")).resolve()
|
||||
project = base / "project"
|
||||
subprocess.run([sys.executable, ROOT / "scripts/prepare-fixture-host.py", "--destination", project,
|
||||
"--target-java-home", args.target_java_home, "--mode", args.mode], check=True)
|
||||
settings = project / ".neitzel-fx-designer/host.properties"
|
||||
file = project / "app/src/main/resources/fixture/components/Person.fxml"
|
||||
env = dict(os.environ)
|
||||
if args.host == "standalone":
|
||||
cp = os.pathsep.join([str(ROOT / "designer-standalone/target/classes"),
|
||||
(ROOT / "designer-standalone/target/smoke-classpath.txt").read_text().strip()])
|
||||
command = [str(args.java_home / "bin/java"), "--enable-native-access=ALL-UNNAMED", "-cp", cp,
|
||||
"de.neitzel.fxdesigner.standalone.Launcher", str(settings), str(file)]
|
||||
else:
|
||||
if args.idea_home is None:
|
||||
p.error("--idea-home is required for IntelliJ")
|
||||
plugin = next((ROOT / "designer-intellij/build/distributions").glob("*.zip"))
|
||||
with zipfile.ZipFile(plugin) as archive:
|
||||
archive.extractall(base / "plugins")
|
||||
if args.check:
|
||||
classes = base / "check-classes"
|
||||
classes.mkdir()
|
||||
jars = [*(args.idea_home / "lib").glob("*.jar"), *(base / "plugins").rglob("*.jar")]
|
||||
subprocess.run([str(args.java_home / "bin/javac"), "-cp", os.pathsep.join(map(str,jars)), "-d", str(classes),
|
||||
str(ROOT / "integration-tests/idea/WorkflowProbe.java")], check=True)
|
||||
meta = classes / "META-INF"
|
||||
meta.mkdir()
|
||||
(meta / "plugin.xml").write_text('<idea-plugin><id>de.neitzel.fxdesigner.qualification</id><name>Designer workflow qualification</name><version>1</version><vendor>Local tests</vendor><idea-version since-build="263" until-build="263.*"/><depends>com.intellij.modules.platform</depends><depends>de.neitzel.fxdesigner</depends><extensions defaultExtensionNs="com.intellij"><postStartupActivity implementation="qualification.WorkflowProbe"/></extensions></idea-plugin>')
|
||||
jar = base / "plugins/qualification/lib/qualification.jar"
|
||||
jar.parent.mkdir(parents=True)
|
||||
subprocess.run([str(args.java_home / "bin/jar"), "cf", str(jar), "-C", str(classes), "."], check=True)
|
||||
options = base / "config/options"
|
||||
options.mkdir(parents=True)
|
||||
(options / "ide.general.xml").write_text('<application><component name="Registry"><entry key="embed.scene.builder" value="false"/></component></application>')
|
||||
(options / "trusted-paths.xml").write_text('<application><component name="Trusted.Paths"><option name="TRUSTED_PROJECT_PATHS"><map><entry key="'+escape(str(project))+'" value="true"/></map></option></component></application>')
|
||||
props = base / "idea.properties"
|
||||
props.write_text("\n".join("idea."+name+".path="+str(base/name) for name in ["config","system","plugins","log"])+"\nidea.initially.ask.config=false\n")
|
||||
vm = base / "idea.vmoptions"
|
||||
vm.write_text((args.idea_home / "bin/idea.vmoptions").read_text()+"\n--enable-native-access=ALL-UNNAMED\n-Dneitzel.designer.launch="+str(settings)+"\n-Ddesigner.check.output="+str(base)+"\n")
|
||||
env.update(IDEA_PROPERTIES=str(props), IDEA_VM_OPTIONS=str(vm), IDEA_JDK=str(args.java_home))
|
||||
command = [str(args.idea_home / "MacOS/idea"), str(project), str(file)]
|
||||
with (base / "launcher.log").open("w") as log:
|
||||
process = subprocess.Popen(command, env=env, stdout=log, stderr=subprocess.STDOUT, start_new_session=True)
|
||||
(base / "host.pid").write_text(str(process.pid))
|
||||
print("Host PID:", process.pid)
|
||||
print("Disposable host:", base)
|
||||
print("FXML:", file)
|
||||
Reference in New Issue
Block a user