From ada30d4e8d65fcfde46b6463180d76455c97a6d6 Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Thu, 17 Feb 2022 16:27:26 +0300 Subject: [PATCH] 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 9deef18d46ffa02a3d6507ccbbf849c71e822196) --- .../classes/sun/lwawt/macosx/LWCToolkit.java | 8 +- .../native/libawt_lwawt/awt/LWCToolkit.m | 17 ++ .../share/classes/sun/awt/AWTThreading.java | 32 +-- .../jb/java/awt/Toolkit/AWTThreadingTest.java | 199 ++++++++++-------- .../Toolkit/LWCToolkitInvokeAndWaitTest.java | 148 ++++++------- .../awt/Toolkit/helper/ToolkitTestHelper.java | 141 +++++++++++++ 6 files changed, 355 insertions(+), 190 deletions(-) create mode 100644 test/jdk/jb/java/awt/Toolkit/helper/ToolkitTestHelper.java diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/LWCToolkit.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/LWCToolkit.java index 30ee24eac34b..31f73b196e34 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/LWCToolkit.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/LWCToolkit.java @@ -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 diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/LWCToolkit.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/LWCToolkit.m index 6ba00eb3147b..d2769a2124cd 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/LWCToolkit.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/LWCToolkit.m @@ -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 diff --git a/src/java.desktop/share/classes/sun/awt/AWTThreading.java b/src/java.desktop/share/classes/sun/awt/AWTThreading.java index 1e21270ac35e..d44b9aaca4d3 100644 --- a/src/java.desktop/share/classes/sun/awt/AWTThreading.java +++ b/src/java.desktop/share/classes/sun/awt/AWTThreading.java @@ -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 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); - } } diff --git a/test/jdk/jb/java/awt/Toolkit/AWTThreadingTest.java b/test/jdk/jb/java/awt/Toolkit/AWTThreadingTest.java index d8eb2333a70e..5e243b3cd213 100644 --- a/test/jdk/jb/java/awt/Toolkit/AWTThreadingTest.java +++ b/test/jdk/jb/java/awt/Toolkit/AWTThreadingTest.java @@ -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 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"); } } \ No newline at end of file diff --git a/test/jdk/jb/java/awt/Toolkit/LWCToolkitInvokeAndWaitTest.java b/test/jdk/jb/java/awt/Toolkit/LWCToolkitInvokeAndWaitTest.java index 9b97dd283184..32e7d5e0e71c 100644 --- a/test/jdk/jb/java/awt/Toolkit/LWCToolkitInvokeAndWaitTest.java +++ b/test/jdk/jb/java/awt/Toolkit/LWCToolkitInvokeAndWaitTest.java @@ -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 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 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 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 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 { diff --git a/test/jdk/jb/java/awt/Toolkit/helper/ToolkitTestHelper.java b/test/jdk/jb/java/awt/Toolkit/helper/ToolkitTestHelper.java new file mode 100644 index 000000000000..8f8f8f69c021 --- /dev/null +++ b/test/jdk/jb/java/awt/Toolkit/helper/ToolkitTestHelper.java @@ -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 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 tryCall(Callable 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; + } + } +}