Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.junit.jupiter.api.Assertions.*;
Expand Down Expand Up @@ -72,14 +74,20 @@ public void onReady(T value, Throwable err) {
* Runs {@code op} off the EDT and waits for its delivery without blocking
* the EDT.
*
* <p>Worth spelling out, because the obvious harness fails in a way that
* looks like a product bug. These tests run on the EDT; the delivery being
* asserted is a {@code callSerially}; so waiting on a latch from the test
* thread stops the event loop and the runnable carrying the result can
* never run. {@code invokeAndBlock} is the CN1 answer -- it moves the
* waiting off the EDT and keeps the loop pumping. My first version of this
* file used a plain latch and reported the aggregate path as broken when
* it was not.</p>
* <p>Worth spelling out, because the harness reads as more than it is.
* JUnit runs these on {@code main}, not on the EDT, so
* {@code invokeAndBlock} takes its non-EDT branch and simply runs the
* operation inline; the real EDT pumps its own loop alongside, which is
* what carries the delivery. The call is kept because it states the
* precondition the assertion below checks -- the operation starts off the
* EDT -- and because it is what a caller would write.</p>
*
* <p>The consequence is that the EDT is free to deliver the moment a
* resource is completed, so every test here has to attach its listener
* before that can happen: an already-settled {@code AsyncResource} runs a
* late listener inline on the attaching thread, recording "not the EDT"
* for a delivery that did happen there. The store tests hold the backend;
* the facade test parks the EDT.</p>
*/
private <T> void assertDeliveredOnEdt(final Landing<T> landing,
final Runnable op) {
Expand Down Expand Up @@ -197,20 +205,6 @@ public void run() {
});
}

/**
* The facade's own actions deliver on the EDT too.
*
* <p>These were missed when the store's results were moved onto
* {@code EdtResult}: {@code openHealthSettings} and
* {@code openProviderSetup} settle synchronously before the method
* returns, so a callback attached afterwards ran immediately on whatever
* thread called -- and a caller doing UI work in it, which is the whole
* point of "did the settings screen open?", raced rendering.</p>
*
* <p>The fallback facade is the one under test here because it is the one
* that settles inline; the port facades do the same thing through the
* same resource type.</p>
*/
/**
* Every public health resource is an EDT-delivering one.
*
Expand Down Expand Up @@ -310,12 +304,50 @@ private static String enclosingMethod(String[] lines, int at) {
return "<unknown>";
}

/**
* The facade's own actions deliver on the EDT too.
*
* <p>These were missed when the store's results were moved onto
* {@code EdtResult}: {@code openHealthSettings} and
* {@code openProviderSetup} settle synchronously before the method
* returns, so a callback attached afterwards ran immediately on whatever
* thread called -- and a caller doing UI work in it, which is the whole
* point of "did the settings screen open?", raced rendering.</p>
*
* <p>The fallback facade is the one under test here because it is the one
* that settles inline; the port facades do the same thing through the
* same resource type.</p>
*/
@Test
void aFacadeActionDeliversOnTheEdt() {
final Landing<Boolean> landing = new Landing<Boolean>();
final CountDownLatch attached = new CountDownLatch(1);
assertDeliveredOnEdt(landing, new Runnable() {
public void run() {
Health.getInstance().openHealthSettings().onResult(landing);
// The facade settles before it returns, so unlike the store
// tests there is nothing to hold back -- park the EDT instead.
// Serial calls run in order, so this blocker is ahead of the
// delivery in the queue and the listener wins the attach.
CN.callSerially(new Runnable() {
public void run() {
try {
// Bounded on purpose: a blocker that outlived its
// release would park the EDT for the rest of the
// suite, turning one failure into a hang.
attached.await(10, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
});
try {
Health.getInstance().openHealthSettings()
.onResult(landing);
} finally {
// In a finally so a throw above surfaces as itself rather
// than as a suite-wide hang behind a still-parked EDT.
attached.countDown();
}
}
}, true);
}
Expand Down
Loading