JBR-4106 PyCharm hangs with 100% CPU usage on one core

with fix for JBR-5300 Change source code and test files to use GPL license

(cherry picked from commit 9deef18d46)
This commit is contained in:
Anton Tarasov
2022-02-17 16:27:26 +03:00
committed by jbrbot
parent 99b04b6c5c
commit 53c149e531
6 changed files with 355 additions and 190 deletions

View File

@@ -475,7 +475,8 @@ public final class LWCToolkit extends LWToolkit {
if (!(gd instanceof CGraphicsDevice)) {
return AWTThreading.executeWaitToolkit(() -> super.getScreenInsets(gc));
}
return AWTThreading.executeWaitToolkit(() -> ((CGraphicsDevice)gd).getScreenInsets());
CGraphicsDevice cgd = (CGraphicsDevice) gd;
return cgd.getScreenInsets();
}
@Override
@@ -823,6 +824,11 @@ public final class LWCToolkit extends LWToolkit {
*/
static native void performOnMainThreadAfterDelay(Runnable r, long delay);
/**
* Schedules a {@code Runnable} execution on the Appkit thread and waits for completion.
*/
public static native void performOnMainThreadAndWait(Runnable r);
// DnD support
@Override

View File

@@ -662,6 +662,23 @@ JNI_COCOA_ENTER(env);
JNI_COCOA_EXIT(env);
}
/*
* Class: sun_lwawt_macosx_LWCToolkit
* Method: performOnMainThreadAndWait
* Signature: (Ljava/lang/Runnable)V
*/
JNIEXPORT void JNICALL Java_sun_lwawt_macosx_LWCToolkit_performOnMainThreadAndWait
(JNIEnv *env, jclass clz, jobject runnable)
{
JNI_COCOA_ENTER(env);
jobject gRunnable = (*env)->NewGlobalRef(env, runnable);
CHECK_NULL(gRunnable);
[ThreadUtilities performOnMainThreadWaiting:YES block:^() {
JavaRunnable* performer = [[JavaRunnable alloc] initWithRunnable:gRunnable];
[performer perform];
}];
JNI_COCOA_EXIT(env);
}
/*
* Class: sun_lwawt_macosx_LWCToolkit

View File

@@ -14,6 +14,7 @@ import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.Consumer;
/**
* Used to perform a cross threads (EventDispatch, Toolkit) execution so that the execution does not cause a deadlock.
@@ -75,14 +76,25 @@ public class AWTThreading {
}
/**
* Same as {@link #executeWaitToolkit(Callable)}, but without returning a value. If requested (as indicated by
* the passed parameter), the invoked native method is supposed to wait for the result of invocation on AppKit
* thread, and vice versa.
* A boolean value passed to the consumer indicates whether the consumer should perform
* a synchronous invocation on the Toolkit thread and wait, or return immediately.
*
* @see #executeWaitToolkit(Callable).
*/
public static void executeWaitToolkit(Task runnable) {
public static void executeWaitToolkit(Consumer<Boolean> consumer) {
boolean wait = EventQueue.isDispatchThread();
executeWaitToolkit(() -> {
runnable.run(wait);
consumer.accept(wait);
return null;
});
}
/**
* @see #executeWaitToolkit(Callable).
*/
public static void executeWaitToolkit(Runnable runnable) {
executeWaitToolkit(() -> {
runnable.run();
return null;
});
}
@@ -102,12 +114,6 @@ public class AWTThreading {
}
}
if (!isEDT && logger.isLoggable(PlatformLogger.Level.FINE)) {
// this can cause deadlock if calling thread is holding a lock which EDT might require (e.g. AWT tree lock)
logger.fine("AWTThreading.executeWaitToolkit invoked from non-EDT thread", new Throwable());
}
// fallback to default
try {
return callable.call();
} catch (Exception e) {
@@ -415,8 +421,4 @@ public class AWTThreading {
future.complete(null);
return future;
}
public interface Task {
void run(boolean wait);
}
}

View File

@@ -1,139 +1,158 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.awt.*;
import javax.swing.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.Arrays;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
import sun.lwawt.macosx.CThreading;
import sun.lwawt.macosx.LWCToolkit;
import sun.awt.AWTThreading;
import static helper.ToolkitTestHelper.*;
/*
* @test
* @summary tests that AWTThreading can manage a stream of cross EDT/AppKit invocation requests
* @summary tests that AWTThreading can manage cross EDT/AppKit blocking invocation requests
* @requires (os.family == "mac")
* @compile --add-exports=java.desktop/sun.lwawt.macosx=ALL-UNNAMED --add-exports=java.desktop/sun.awt=ALL-UNNAMED AWTThreadingTest.java
* @run main/othervm --add-exports=java.desktop/sun.lwawt.macosx=ALL-UNNAMED --add-exports=java.desktop/sun.awt=ALL-UNNAMED AWTThreadingTest
* @modules java.desktop/sun.lwawt.macosx java.desktop/sun.awt
* @run main AWTThreadingTest
* @author Anton Tarasov
*/
@SuppressWarnings("ConstantConditions")
public class AWTThreadingTest {
static final ReentrantLock LOCK = new ReentrantLock();
static final Condition COND = LOCK.newCondition();
static final CountDownLatch LATCH = new CountDownLatch(1);
static final int TIMEOUT_SECONDS = 1;
static JFrame frame;
static Thread thread;
static final AtomicInteger ITER_COUNTER = new AtomicInteger();
static final AtomicBoolean DUMP_STACK = new AtomicBoolean(false);
final static AtomicBoolean passed = new AtomicBoolean(true);
final static AtomicInteger counter = new AtomicInteger(0);
static volatile Thread THREAD;
public static void main(String[] args) throws InterruptedException {
EventQueue.invokeLater(AWTThreadingTest::runGui);
public static void main(String[] args) {
DUMP_STACK.set(args.length > 0 && "dumpStack".equals(args[0]));
LATCH.await(5, TimeUnit.SECONDS);
initTest(AWTThreadingTest.class);
frame.dispose();
thread.interrupt();
testCase("certain threads superposition", AWTThreadingTest::test);
testCase("random threads superposition", AWTThreadingTest::test);
if (!passed.get()) {
throw new RuntimeException("Test FAILED!");
}
System.out.println("Test PASSED");
}
static void runGui() {
frame = new JFrame("frame");
frame.getContentPane().setBackground(Color.green);
frame.setLocationRelativeTo(null);
frame.setSize(200, 200);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
startThread();
static void test() {
ITER_COUNTER.set(0);
var timer = new TestTimer(TIMEOUT_SECONDS * 3, TimeUnit.SECONDS);
EventQueue.invokeLater(() -> startThread(() ->
FUTURE.isDone() ||
timer.hasFinished()));
tryRun(() -> {
if (!FUTURE.get(TIMEOUT_SECONDS * 4, TimeUnit.SECONDS)) {
throw new RuntimeException("Test FAILED! (negative result)");
}
});
frame.setVisible(true);
tryRun(THREAD::join);
System.out.println(ITER_COUNTER + " iterations passed");
}
static void startThread() {
thread = new Thread(() -> {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
break;
}
static void startThread(Supplier<Boolean> shouldExitLoop) {
THREAD = new Thread(() -> {
while (!shouldExitLoop.get()) {
ITER_COUNTER.incrementAndGet();
var point_1 = new CountDownLatch(1);
var point_2 = new CountDownLatch(1);
var point_3 = new CountDownLatch(1);
var invocations = new CountDownLatch(2);
//
// 1. Execute invokeAndWait() from AppKit to EDT
// 1. Blocking invocation from AppKit to EDT
//
CThreading.executeOnAppKit(() -> {
try {
LWCToolkit.invokeAndWait(counter::incrementAndGet, Window.getWindows()[0]);
} catch (Exception e) {
fail(e);
}
// We're on AppKit, wait for the 2nd invocation to be on the AWTThreading-pool thread.
if (TEST_CASE == 1) await(point_1, TIMEOUT_SECONDS);
tryRun(() -> LWCToolkit.invokeAndWait(() -> {
// We're being dispatched on EDT.
if (TEST_CASE == 1) point_2.countDown();
// Wait for the 2nd invocation to be executed on AppKit.
if (TEST_CASE == 1) await(point_3, TIMEOUT_SECONDS);
}, FRAME));
invocations.countDown();
});
//
// 2. Execute invokeAndBlock() from EDT to AppKit
// 2. Blocking invocation from EDT to AppKit
//
EventQueue.invokeLater(() -> {
passed.set(false);
EventQueue.invokeLater(() -> AWTThreading.executeWaitToolkit(() -> {
// We're on the AWTThreading-pool thread.
if (TEST_CASE == 1) point_1.countDown();
Boolean success = AWTThreading.executeWaitToolkit(() -> {
try {
return CThreading.executeOnAppKit(() -> Boolean.TRUE);
} catch (Throwable e) {
fail(e);
// Wait for the 1st invocation to start NSRunLoop and be dispatched
if (TEST_CASE == 1) await(point_2, TIMEOUT_SECONDS);
// Perform in JavaRunLoopMode to be accepted by NSRunLoop started by LWCToolkit.invokeAndWait.
LWCToolkit.performOnMainThreadAndWait(() -> {
if (DUMP_STACK.get()) {
dumpAllThreads();
}
return null;
// We're being executed on AppKit.
if (TEST_CASE == 1) point_3.countDown();
});
System.out.println("Success: " + counter.get() + ": " + success);
passed.set(Boolean.TRUE.equals(success));
invocations.countDown();
}));
if (passed.get()) {
lock(COND::signal);
}
else {
fail(null);
}
});
await(invocations, TIMEOUT_SECONDS * 2);
} // while
lock(COND::await);
}
FUTURE.complete(true);
});
thread.setDaemon(true);
thread.start();
THREAD.setDaemon(true);
THREAD.start();
}
static void lock(MyRunnable runnable) {
LOCK.lock();
try {
try {
runnable.run();
} catch (Exception e) {
e.printStackTrace();
}
} finally {
LOCK.unlock();
static void await(CountDownLatch latch, int seconds) {
if (!tryCall(() -> latch.await(seconds, TimeUnit.SECONDS), false)) {
FUTURE.completeExceptionally(new Throwable("Awaiting has timed out"));
}
}
interface MyRunnable {
void run() throws Exception;
}
static void fail(Throwable e) {
if (e != null) e.printStackTrace();
passed.set(false);
LATCH.countDown();
static void dumpAllThreads() {
Thread.getAllStackTraces().keySet().forEach(t -> {
System.out.printf("%s\t%s\t%d\t%s\n", t.getName(), t.getState(), t.getPriority(), t.isDaemon() ? "Daemon" : "Normal");
Arrays.asList(t.getStackTrace()).forEach(frame -> System.out.println("\t" + frame));
});
System.out.println("\n\n");
}
}

View File

@@ -1,7 +1,28 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.awt.*;
import javax.swing.*;
import java.awt.event.InvocationEvent;
import java.util.Optional;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.logging.*;
@@ -11,6 +32,8 @@ import sun.awt.AWTThreading;
import sun.lwawt.macosx.CThreading;
import sun.lwawt.macosx.LWCToolkit;
import static helper.ToolkitTestHelper.*;
/*
* @test
* @summary Tests different scenarios for LWCToolkit.invokeAndWait().
@@ -23,16 +46,12 @@ import sun.lwawt.macosx.LWCToolkit;
@SuppressWarnings("ConstantConditions")
public class LWCToolkitInvokeAndWaitTest {
// This property is used in {CAccessibility}
private static final int INVOKE_TIMEOUT_SECONDS = Integer.getInteger("sun.lwawt.macosx.CAccessibility.invokeTimeoutSeconds", 1);
static TestLogHandler LOG_HANDLER = new TestLogHandler();
static volatile CompletableFuture<Boolean> FUTURE;
static volatile CountDownLatch EDT_FAST_FREE_LATCH;
static volatile JFrame FRAME;
static volatile Thread MAIN_THREAD;
static int TEST_COUNTER;
static final int INVOKE_TIMEOUT_SECONDS = Integer.getInteger("sun.lwawt.macosx.CAccessibility.invokeTimeoutSeconds", 1);
static final Runnable CONSUME_DISPATCHING = () -> FUTURE.completeExceptionally(new Throwable("Unexpected dispatching!"));
static final TestLogHandler LOG_HANDLER = new TestLogHandler();
static volatile CountDownLatch EDT_FAST_FREE_LATCH;
static {
AWTThreading.setAWTThreadingFactory(edt -> new AWTThreading(edt) {
@@ -41,7 +60,7 @@ public class LWCToolkitInvokeAndWaitTest {
if (EDT_FAST_FREE_LATCH != null) {
// 1. wait for the invocation event to be dispatched
// 2. wait for EDT to become free
trycatch(() -> EDT_FAST_FREE_LATCH.await());
tryRun(EDT_FAST_FREE_LATCH::await);
EDT_FAST_FREE_LATCH = null;
}
@@ -62,9 +81,7 @@ public class LWCToolkitInvokeAndWaitTest {
}
public static void main(String[] args) {
MAIN_THREAD = Thread.currentThread();
trycatch(() -> {
tryRun(() -> {
Logger log = LogManager.getLogManager().getLogger(AWTThreading.class.getName());
log.setUseParentHandlers(false);
log.addHandler(LOG_HANDLER);
@@ -75,69 +92,50 @@ public class LWCToolkitInvokeAndWaitTest {
Consumer<InvocationEvent> noop = e -> {};
try {
trycatch(() -> EventQueue.invokeAndWait(LWCToolkitInvokeAndWaitTest::runGui));
initTest(LWCToolkitInvokeAndWaitTest.class);
test("InvocationEvent is normally dispatched",
"",
noop,
() -> System.out.println("I'm dispatched"));
testCase("InvocationEvent is normally dispatched", () -> test(
"",
noop,
() -> System.out.println("I'm dispatched")));
test("InvocationEvent is lost",
"lost",
noop,
CONSUME_DISPATCHING);
testCase("InvocationEvent is lost", () -> test(
"lost",
noop,
CONSUME_DISPATCHING));
EDT_FAST_FREE_LATCH = new CountDownLatch(2);
test("InvocationEvent is lost (EDT becomes fast free)",
"lost",
// notify the invocationEvent has been dispatched
invocationEvent -> EDT_FAST_FREE_LATCH.countDown(),
CONSUME_DISPATCHING);
EDT_FAST_FREE_LATCH = new CountDownLatch(2);
testCase("InvocationEvent is lost (EDT becomes fast free)", () -> test(
"lost",
// notify the invocationEvent has been dispatched
invocationEvent -> EDT_FAST_FREE_LATCH.countDown(),
CONSUME_DISPATCHING));
test("InvocationEvent is disposed",
"disposed",
invocationEvent -> AWTAccessor.getInvocationEventAccessor().dispose(invocationEvent),
CONSUME_DISPATCHING);
testCase("InvocationEvent is disposed", () -> test(
"disposed",
invocationEvent -> AWTAccessor.getInvocationEventAccessor().dispose(invocationEvent),
CONSUME_DISPATCHING));
test("InvocationEvent is timed out (delayed before dispatching)",
"timed out",
invocationEvent -> sleep(INVOKE_TIMEOUT_SECONDS * 4),
CONSUME_DISPATCHING);
testCase("InvocationEvent is timed out (delayed before dispatching)", () -> test(
"timed out",
invocationEvent -> sleep(INVOKE_TIMEOUT_SECONDS * 4),
CONSUME_DISPATCHING));
test("InvocationEvent is timed out (delayed during dispatching)",
"timed out",
noop,
() -> sleep(INVOKE_TIMEOUT_SECONDS * 4));
testCase("InvocationEvent is timed out (delayed during dispatching)", () -> test(
"timed out",
noop,
() -> sleep(INVOKE_TIMEOUT_SECONDS * 4)));
} finally {
FRAME.dispose();
}
System.out.println("Test PASSED");
}
static void runGui() {
FRAME = new JFrame(LWCToolkitInvokeAndWaitTest.class.getSimpleName());
FRAME.getContentPane().setBackground(Color.green);
FRAME.setLocationRelativeTo(null);
FRAME.setSize(200, 200);
FRAME.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
FRAME.setVisible(true);
}
static void test(String testCaption,
String expectedInLog,
static void test(String expectedInLog,
Consumer<InvocationEvent> onBeforeDispatching,
Runnable onDispatching)
{
System.out.println("\n(" + (++TEST_COUNTER) + ") TEST: " + testCaption);
FUTURE = new CompletableFuture<>();
FUTURE.whenComplete((r, ex) -> Optional.of(ex).ifPresent(Throwable::printStackTrace));
EventQueue.invokeLater(() -> subTest(onBeforeDispatching, onDispatching));
trycatch(() -> {
tryRun(() -> {
if (!FUTURE.get(INVOKE_TIMEOUT_SECONDS * 2L, TimeUnit.SECONDS)) {
throw new RuntimeException("Test FAILED! (negative result)");
}
@@ -146,14 +144,12 @@ public class LWCToolkitInvokeAndWaitTest {
// let AppKit and EDT print all the logging
var latch = new CountDownLatch(1);
CThreading.executeOnAppKit(latch::countDown);
trycatch(latch::await);
trycatch(() -> EventQueue.invokeAndWait(() -> {}));
tryRun(latch::await);
tryRun(() -> EventQueue.invokeAndWait(EMPTY_RUNNABLE));
if (!LOG_HANDLER.testContains(expectedInLog)) {
throw new RuntimeException("Test FAILED! (not found in the log: \"" + expectedInLog + "\")");
}
System.out.println("(" + TEST_COUNTER + ") SUCCEEDED\n");
}
static void subTest(Consumer<InvocationEvent> onBeforeDispatching, Runnable onDispatching) {
@@ -175,7 +171,7 @@ public class LWCToolkitInvokeAndWaitTest {
super.dispatchEvent(event);
}
});
CThreading.executeOnAppKit(() -> trycatch(() -> {
CThreading.executeOnAppKit(() -> tryRun(() -> {
//
// Post an invocation from AppKit.
//
@@ -185,23 +181,7 @@ public class LWCToolkitInvokeAndWaitTest {
}
static void sleep(int seconds) {
trycatch(() -> Thread.sleep(seconds * 1000L));
}
static void trycatch(ThrowableRunnable runnable) {
try {
runnable.run();
} catch (Exception e) {
if (Thread.currentThread() == MAIN_THREAD) {
throw new RuntimeException("Test FAILED!", e);
} else {
FUTURE.completeExceptionally(e);
}
}
}
interface ThrowableRunnable {
void run() throws Exception;
tryRun(() -> Thread.sleep(seconds * 1000L));
}
static class TestLogHandler extends StreamHandler {

View File

@@ -0,0 +1,141 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package helper;
import javax.swing.*;
import java.awt.*;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@SuppressWarnings("ConstantConditions")
public class ToolkitTestHelper {
public static final Runnable EMPTY_RUNNABLE = () -> {};
public static volatile CompletableFuture<Boolean> FUTURE;
public static volatile int TEST_CASE;
public static volatile JFrame FRAME;
private static volatile JLabel LABEL;
private static volatile Thread MAIN_THREAD;
private static final Runnable UPDATE_LABEL = new Runnable() {
final Random rand = new Random();
@Override
public void run() {
LABEL.setForeground(new Color(rand.nextFloat(), 0, rand.nextFloat()));
LABEL.setText("(" + TEST_CASE + ")");
}
};
public static void initTest(Class<?> testClass) {
MAIN_THREAD = Thread.currentThread();
assert MAIN_THREAD.getName().toLowerCase().contains("main");
Thread.UncaughtExceptionHandler handler = MAIN_THREAD.getUncaughtExceptionHandler();
MAIN_THREAD.setUncaughtExceptionHandler((t, e) -> {
if (FRAME != null) FRAME.dispose();
handler.uncaughtException(t, e);
});
tryRun(() -> EventQueue.invokeAndWait(() -> {
FRAME = new JFrame(testClass.getSimpleName());
LABEL = new JLabel("0");
LABEL.setFont(LABEL.getFont().deriveFont((float)30));
LABEL.setHorizontalAlignment(SwingConstants.CENTER);
FRAME.add(LABEL, BorderLayout.CENTER);
FRAME.getContentPane().setBackground(Color.green);
FRAME.setLocationRelativeTo(null);
FRAME.setSize(200, 200);
FRAME.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
FRAME.setVisible(true);
}));
Timer timer = new Timer(100, e -> UPDATE_LABEL.run());
timer.setRepeats(true);
timer.start();
new Thread(() -> {
try {
MAIN_THREAD.join();
} catch (InterruptedException ignored) {
}
if (FRAME != null) FRAME.dispose();
timer.stop();
}).start();
}
public static void testCase(String caseCaption, Runnable test) {
FUTURE = new CompletableFuture<>();
FUTURE.whenComplete((r, e) -> Optional.of(e).ifPresent(Throwable::printStackTrace));
//noinspection NonAtomicOperationOnVolatileField
String prefix = "(" + (++TEST_CASE) + ")";
System.out.println("\n" + prefix + " TEST: " + caseCaption);
UPDATE_LABEL.run();
test.run();
System.out.println(prefix + " SUCCEEDED\n");
}
public static void tryRun(ThrowableRunnable runnable) {
tryCall(() -> {
runnable.run();
return null;
}, null);
}
public static <T> T tryCall(Callable<T> callable, T defValue) {
try {
return callable.call();
} catch (Exception e) {
if (Thread.currentThread() == MAIN_THREAD) {
throw new RuntimeException("Test FAILED!", e);
} else {
FUTURE.completeExceptionally(e);
}
}
return defValue;
}
public interface ThrowableRunnable {
void run() throws Exception;
}
public static class TestTimer {
private final long finishTime;
public TestTimer(long timeFromNow, TimeUnit unit) {
finishTime = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(timeFromNow, unit);
}
public boolean hasFinished() {
return System.currentTimeMillis() >= finishTime;
}
}
}