Validate preview sessions against real classpath and module-path fixtures
This commit is contained in:
+197
@@ -0,0 +1,197 @@
|
||||
package fixture.checks;
|
||||
|
||||
import de.neitzel.fxdesigner.preview.*;
|
||||
import de.neitzel.fxdesigner.target.*;
|
||||
import fixture.components.*;
|
||||
import fixture.model.*;
|
||||
import fixture.preview.Examples;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
|
||||
public final class PreviewChecks {
|
||||
static int checks;
|
||||
|
||||
static void check(boolean b, String message) {
|
||||
if (!b) throw new AssertionError(message);
|
||||
checks++;
|
||||
}
|
||||
|
||||
static <T> T fx(Callable<T> action) throws Exception {
|
||||
var result = new CompletableFuture<T>();
|
||||
Platform.runLater(
|
||||
() -> {
|
||||
try {
|
||||
result.complete(action.call());
|
||||
} catch (Throwable e) {
|
||||
result.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
return result.get(10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
static PreviewFailure failure(Throwable error) {
|
||||
for (Throwable t = error; t != null; t = t.getCause())
|
||||
if (t instanceof PreviewFailure f) return f;
|
||||
throw new AssertionError("No structured preview diagnostic", error);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String mode = args[0];
|
||||
ClassLoader loader = PreviewChecks.class.getClassLoader();
|
||||
var descriptors = ExampleCatalog.discover(loader);
|
||||
check(descriptors.size() == 9, "Descriptor discovery");
|
||||
check(
|
||||
System.getProperty("fixture.provider.initialized") == null,
|
||||
"Discovery initialized provider");
|
||||
check(System.getProperty("fixture.models") == null, "Discovery constructed Model");
|
||||
check(System.getProperty("fixture.controllers") == null, "Discovery constructed Controller");
|
||||
if (mode.equals("discovery")) {
|
||||
System.out.println("PASS discovery " + checks);
|
||||
return;
|
||||
}
|
||||
var started = new CountDownLatch(1);
|
||||
Platform.startup(
|
||||
() -> {
|
||||
Platform.setImplicitExit(false);
|
||||
started.countDown();
|
||||
});
|
||||
check(started.await(10, TimeUnit.SECONDS), "FX startup");
|
||||
TargetPreview preview = null;
|
||||
try {
|
||||
String id = mode.startsWith("missing-") ? "standard" : mode;
|
||||
var descriptor =
|
||||
descriptors.stream().filter(d -> d.id().equals(id)).findFirst().orElseThrow();
|
||||
preview =
|
||||
TargetPreview.open(
|
||||
descriptor, loader, Duration.ofMillis(mode.equals("timeout") ? 2000 : 10000));
|
||||
if (mode.equals("async") || mode.equals("cancel")) {
|
||||
for (int i = 0; i < 100 && Examples.pending == null; i++) Thread.sleep(20);
|
||||
check(Examples.pending != null, "Provider readiness created");
|
||||
check(!preview.ready().toCompletableFuture().isDone(), "Premature readiness");
|
||||
if (mode.equals("async")) Examples.pending.complete(null);
|
||||
else preview.cancel();
|
||||
}
|
||||
if (java.util.Set.of(
|
||||
"timeout",
|
||||
"cancel",
|
||||
"readiness-failure",
|
||||
"wrong-model",
|
||||
"invalid-attribute",
|
||||
"factory-failure",
|
||||
"missing-components",
|
||||
"missing-model")
|
||||
.contains(mode)) {
|
||||
try {
|
||||
preview.ready().toCompletableFuture().get(15, TimeUnit.SECONDS);
|
||||
throw new AssertionError("Failure accepted: " + mode);
|
||||
} catch (ExecutionException error) {
|
||||
var f = failure(error);
|
||||
var expected =
|
||||
switch (mode) {
|
||||
case "timeout" -> PreviewFailure.Code.TIMEOUT;
|
||||
case "cancel" -> PreviewFailure.Code.CANCELLED;
|
||||
case "readiness-failure" -> PreviewFailure.Code.READINESS;
|
||||
case "wrong-model" -> PreviewFailure.Code.MODEL_TYPE;
|
||||
case "missing-components", "missing-model" -> PreviewFailure.Code.MODULE_ACCESS;
|
||||
default -> PreviewFailure.Code.LOAD;
|
||||
};
|
||||
check(f.code() == expected, "Wrong diagnostic: " + f.code() + " expected " + expected);
|
||||
}
|
||||
preview.cleanup().toCompletableFuture().get(5, TimeUnit.SECONDS);
|
||||
if (!mode.equals("missing-components")) {
|
||||
check(Examples.service.closes == 1, "Failure did not clean services once");
|
||||
if (!mode.equals("factory-failure"))
|
||||
check(Examples.closes == 1, "Session cleanup missing");
|
||||
}
|
||||
} else {
|
||||
TargetPreview ready = preview.ready().toCompletableFuture().get(15, TimeUnit.SECONDS);
|
||||
fx(
|
||||
() -> {
|
||||
var view = ready.loadedView();
|
||||
check(view.getViewModel() instanceof PersonViewModel, "Named ViewModel not selected");
|
||||
var controller = Examples.context.getComponent(PersonController.class);
|
||||
check(controller.received == view.getViewModel(), "Controller ViewModel handoff");
|
||||
check(controller.greeting == Examples.service, "Configured service not injected");
|
||||
var card = (Card) view.getRoot().lookup("#card");
|
||||
var second = (Card) view.getRoot().lookup("#second");
|
||||
check(card != second, "Component instances reused");
|
||||
check(
|
||||
card.getModel() == Examples.person.getHome()
|
||||
&& second.getModel() == card.getModel(),
|
||||
"Nested Model identity not shared");
|
||||
check(card.received == second.received, "Nested ViewModel identity changed");
|
||||
check(
|
||||
card.greeting == Examples.service && second.greeting == Examples.service,
|
||||
"Component injection");
|
||||
check(card.getCaption().equals("FXML caption"), "Custom caption lost");
|
||||
check(
|
||||
card.getPrefWidth() == 310 && card.getAlignment() == Pos.CENTER_LEFT,
|
||||
"Inherited/coerced attributes lost");
|
||||
check(card.injected, "Component initialization");
|
||||
TextField name = (TextField) view.getRoot().lookup("#name");
|
||||
check(name.getText().equals("Ada"), "Initial parent binding");
|
||||
name.setText("Grace");
|
||||
check(Examples.person.getName().equals("Grace"), "Parent write-back");
|
||||
card.street().setText("Changed Street");
|
||||
check(
|
||||
Examples.person.getHome().getStreet().equals("Changed Street")
|
||||
&& second.street().getText().equals("Changed Street"),
|
||||
"Component write-back and shared Model");
|
||||
check(
|
||||
((Label) view.getRoot().lookup("#greeting")).getText().startsWith("configured-"),
|
||||
"Computed controller binding");
|
||||
byte[] rgba = ready.render(420, 240);
|
||||
check(rgba.length == 420 * 240 * 4, "Render size");
|
||||
boolean nonzero = false;
|
||||
for (byte value : rgba)
|
||||
if (value != 0) {
|
||||
nonzero = true;
|
||||
break;
|
||||
}
|
||||
check(nonzero, "Blank rendering");
|
||||
try {
|
||||
ready.render(8192, 8192);
|
||||
throw new AssertionError("Oversized image allowed");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
checks++;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
System.out.println(
|
||||
"SESSION "
|
||||
+ Examples.service.identity
|
||||
+ " models="
|
||||
+ System.getProperty("fixture.models"));
|
||||
preview.close();
|
||||
try {
|
||||
preview.cleanup().toCompletableFuture().get(5, TimeUnit.SECONDS);
|
||||
check(!mode.equals("cleanup-failure"), "Cleanup failure hidden");
|
||||
} catch (ExecutionException error) {
|
||||
check(
|
||||
mode.equals("cleanup-failure")
|
||||
&& failure(error).code() == PreviewFailure.Code.CLEANUP,
|
||||
"Wrong cleanup diagnostic");
|
||||
}
|
||||
check(
|
||||
Examples.closes == 1 && Examples.service.closes == 1,
|
||||
"Owned resources not cleaned exactly once");
|
||||
preview.close();
|
||||
check(Examples.closes == 1, "Repeated close cleaned twice");
|
||||
}
|
||||
try {
|
||||
TargetPreview.open(descriptor, loader, Duration.ofSeconds(1));
|
||||
throw new AssertionError("Process session reused");
|
||||
} catch (PreviewFailure e) {
|
||||
check(e.code() == PreviewFailure.Code.SESSION_REUSE, "Wrong reuse diagnostic");
|
||||
}
|
||||
System.out.println("PASS " + mode + " " + checks);
|
||||
} finally {
|
||||
if (preview != null) preview.close();
|
||||
Platform.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
module fixture.checks {
|
||||
requires fixture.app;
|
||||
requires fixture.preview;
|
||||
requires de.neitzel.fxdesigner.target;
|
||||
requires de.neitzel.fxdesigner.preview;
|
||||
requires fx;
|
||||
requires injection;
|
||||
requires javafx.controls;
|
||||
requires javafx.fxml;
|
||||
}
|
||||
Reference in New Issue
Block a user