Compare commits

..

1 Commits

Author SHA1 Message Date
Vitaly Provodin
0b382484ee Revert "JBR-4306 Robot doesn't work as expected in some cases on macOS"
This reverts commit 803039d6db.
2022-03-27 08:47:16 +07:00
16 changed files with 320 additions and 802 deletions

View File

@@ -1,16 +1,5 @@
#!/bin/bash
while getopts ":t" o; do
case "${o}" in
t)
t="With Teamcity tests info"
TC_PRINT=1
;;
esac
done
shift $((OPTIND-1))
NEWFILEPATH=$1
CONFIGID=$2
BUILDID=$3
@@ -18,7 +7,6 @@ TOKEN=$4
#
# Get the size of new artifact
#
unameOut="$(uname -s)"
case "${unameOut}" in
Linux*)
@@ -28,11 +16,7 @@ case "${unameOut}" in
NEWFILESIZE=$(stat -f%z "$NEWFILEPATH")
;;
CYGWIN*)
NEWFILESIZE=$(stat -c%s$4
#
# Get the size of new artifact
#
"$NEWFILEPATH")
NEWFILESIZE=$(stat -c%s "$NEWFILEPATH")
;;
MINGW*)
NEWFILESIZE=$(stat -c%s "$NEWFILEPATH")
@@ -42,7 +26,6 @@ case "${unameOut}" in
exit 1
esac
FILENAME=$(basename ${NEWFILEPATH})
#
# Get pattern of artifact name
# Base filename pattern: <BUNDLE_TYPE>-<JDK_VERSION>-<OS>-<ARCH>-b<BUILD>.tar.gz: jbr_dcevm-17.0.2-osx-x64-b1234.tar.gz
@@ -51,40 +34,16 @@ FILENAME=$(basename ${NEWFILEPATH})
BUNDLE_TYPE=jbrsdk
OS_ARCH_PATTERN=""
FILE_EXTENSION=tar.gz
re='(jbr[a-z_]*).*-[0-9_\.]+-(.+)-b[0-9]+(.+)'
re='(jbr[a-z_]*).+[0-9_\.]+-(.+)-b.+\.tar\.gz'
if [[ $FILENAME =~ $re ]]; then
BUNDLE_TYPE=${BASH_REMATCH[1]}
OS_ARCH_PATTERN=${BASH_REMATCH[2]}
FILE_EXTENSION=${BASH_REMATCH[3]}
fi
if [ $TC_PRINT -eq 1 ]; then
testname_file_ext=`echo $FILE_EXTENSION | sed 's/\./_/g'`
testname=$BUNDLE_TYPE"_"$OS_ARCH_PATTERN$testname_file_ext
echo \#\#teamcity[testStarted name=\'$testname\']
fi
echo "BUNDLE_TYPE: " $BUNDLE_TYPE
echo "OS_ARCH_PATTERN: " $OS_ARCH_PATTERN
echo "FILE_EXTENSION: " $FILE_EXTENSION
echo "New size of $FILENAME = $NEWFILESIZE bytes."
function test_failed_msg() {
if [ $3 -eq 1 ]; then
echo \#\#teamcity[testFailed name=\'$1\' message=\'$2\']
fi
}
function test_finished_msg() {
if [ $2 -eq 1 ]; then
echo \#\#teamcity[testFinished name=\'$1\']
fi
}
#
# Get previous successful build ID
# Example:
@@ -103,11 +62,8 @@ if [[ $CURL_RESPONSE =~ $re ]]; then
ID=${BASH_REMATCH[1]}
echo "BUILD Number: ${BASH_REMATCH[2]}"
else
msg="ERROR: can't find previous build"
echo $msg
echo "ERROR: can't find previous build"
echo $CURL_RESPONSE
test_failed_msg $testname $msg $TC_PRINT
test_finished_msg $testname $TC_PRINT
exit 1
fi
@@ -122,7 +78,7 @@ echo "Atrifacts of previous build of $CONFIGID :"
echo $CURL_RESPONSE
# Find binary size (in response) with reg exp
re='name=\"('$BUNDLE_TYPE'[^\"]+'${OS_ARCH_PATTERN}'[^\"]+'${FILE_EXTENSION}')\" size=\"([0-9]+)\"'
re='name=\"('$BUNDLE_TYPE'[^\"]+'${OS_ARCH_PATTERN}'[^\"]+\.tar\.gz)\" size=\"([0-9]+)\"'
if [[ $CURL_RESPONSE =~ $re ]]; then
OLDFILENAME=${BASH_REMATCH[1]}
@@ -133,20 +89,14 @@ if [[ $CURL_RESPONSE =~ $re ]]; then
let allowedSize=OLDFILESIZE+OLDFILESIZE/20 # use 5% threshold
echo "Allowed size = $allowedSize"
if [[ "$NEWFILESIZE" -gt "$allowedSize" ]]; then
msg="ERROR: new size is significally greater than prev size (need to investigate)"
echo $msg
test_failed_msg $testname $msg $TC_PRINT
test_finished_msg $testname $TC_PRINT
echo "ERROR: new size is significally greater than prev size (need to investigate)"
exit 1
else
echo "PASSED"
test_finished_msg $testname $TC_PRINT
fi
else
msg="ERROR: can't find string with size in xml response:"
echo $msg
echo "ERROR: can't find string with size in xml response:"
echo $CURL_RESPONSE
test_failed_msg $testname $msg $TC_PRINT
test_finished_msg $testname $TC_PRINT
exit 1
fi

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -59,7 +59,6 @@ import java.awt.peer.KeyboardFocusManagerPeer;
import java.awt.peer.WindowPeer;
import java.util.List;
import java.util.Objects;
import javax.swing.JComponent;
import sun.awt.AWTAccessor;
@@ -284,16 +283,6 @@ public class LWWindowPeer
super.disposeImpl();
}
@Override
public void setBackground(final Color c) {
Color oldBg = getBackground();
if (Objects.equals(oldBg, c)) {
return;
}
super.setBackground(c);
updateOpaque();
}
@Override
protected void setVisibleImpl(final boolean visible) {
if (!visible && warningWindow != null) {
@@ -302,6 +291,10 @@ public class LWWindowPeer
updateFocusableWindowState();
super.setVisibleImpl(visible);
// TODO: update graphicsConfig, see 4868278
if (visible) {
// Set correct background for a window before making it visible
platformWindow.setOpaque(!isTranslucent());
}
platformWindow.setVisible(visible);
}

View File

@@ -25,7 +25,6 @@
package sun.lwawt.macosx;
import sun.awt.AWTThreading;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
@@ -65,14 +64,14 @@ public class CMenu extends CMenuItem implements MenuPeer {
LWCToolkit.targetToPeer(getTarget().getParent());
if (parent instanceof CMenu) {
return AWTThreading.executeWaitToolkit(() -> parent.executeGet(this::nativeCreateSubMenu));
return parent.executeGet(this::nativeCreateSubMenu);
}
if (parent instanceof CMenuBar) {
MenuBar parentContainer = (MenuBar)getTarget().getParent();
boolean isHelpMenu = parentContainer.getHelpMenu() == getTarget();
int insertionLocation = ((CMenuBar)parent).getNextInsertionIndex();
return AWTThreading.executeWaitToolkit(() -> parent.executeGet(ptr -> nativeCreateMenu(ptr, isHelpMenu,
insertionLocation)));
return parent.executeGet(ptr -> nativeCreateMenu(ptr, isHelpMenu,
insertionLocation));
}
throw new InternalError("Parent must be CMenu or CMenuBar");
}
@@ -85,7 +84,7 @@ public class CMenu extends CMenuItem implements MenuPeer {
@Override
public final void delItem(final int index) {
AWTThreading.executeWaitToolkit(() -> execute(ptr -> nativeDeleteItem(ptr, index)));
execute(ptr -> nativeDeleteItem(ptr, index));
}
@Override

View File

@@ -33,7 +33,6 @@ import java.awt.event.KeyEvent;
import java.awt.peer.MenuItemPeer;
import java.util.concurrent.atomic.AtomicBoolean;
import sun.awt.AWTThreading;
import sun.awt.SunToolkit;
import sun.lwawt.LWToolkit;
@@ -62,7 +61,7 @@ public class CMenuItem extends CMenuComponent implements MenuItemPeer {
@Override
long createModel() {
CMenuComponent parent = (CMenuComponent)LWToolkit.targetToPeer(getTarget().getParent());
return AWTThreading.executeWaitToolkit(() -> parent.executeGet(ptr->nativeCreate(ptr, isSeparator())));
return parent.executeGet(ptr->nativeCreate(ptr, isSeparator()));
}
@SuppressWarnings("deprecation")
public void setLabel(String label, char keyChar, int keyCode, int modifiers) {

View File

@@ -112,7 +112,6 @@ import sun.lwawt.PlatformComponent;
import sun.lwawt.PlatformDropTarget;
import sun.lwawt.PlatformWindow;
import sun.lwawt.SecurityWarningWindow;
import sun.security.action.GetBooleanAction;
import sun.util.logging.PlatformLogger;
@SuppressWarnings("serial") // JDK implementation class
@@ -142,13 +141,6 @@ public final class LWCToolkit extends LWToolkit {
private static CInputMethodDescriptor sInputMethodDescriptor;
// Listens to EDT state in invokeAndWait() and disposes the invocation event
// when EDT becomes free but the invocation event is not yet dispatched (considered lost).
// This prevents a deadlock and makes the invocation return some default result.
@SuppressWarnings("removal")
private static final boolean DISPOSE_INVOCATION_ON_EDT_FREE =
AccessController.doPrivileged(new GetBooleanAction("sun.lwawt.macosx.LWCToolkit.invokeAndWait.disposeOnEDTFree"));
static {
System.err.flush();
@@ -195,7 +187,7 @@ public final class LWCToolkit extends LWToolkit {
*/
private static final boolean inAWT;
private static final PlatformLogger log = PlatformLogger.getLogger(LWCToolkit.class.getName());
private static final PlatformLogger log = PlatformLogger.getLogger("sun.lwawt.macosx.LWCToolkit");
public LWCToolkit() {
final String extraButtons = "sun.awt.enableExtraMouseButtons";
@@ -504,8 +496,7 @@ public final class LWCToolkit extends LWToolkit {
if (!(gd instanceof CGraphicsDevice)) {
return AWTThreading.executeWaitToolkit(() -> super.getScreenInsets(gc));
}
CGraphicsDevice cgd = (CGraphicsDevice) gd;
return cgd.getScreenInsets();
return AWTThreading.executeWaitToolkit(() -> ((CGraphicsDevice)gd).getScreenInsets());
}
@Override
@@ -720,6 +711,30 @@ public final class LWCToolkit extends LWToolkit {
}
}
private static final AtomicInteger blockingRunLoopCounter = new AtomicInteger(0);
private static final AtomicBoolean priorityInvocationPending = new AtomicBoolean(false);
@Override
public void unsafeNonblockingExecute(Runnable runnable) {
if (!EventQueue.isDispatchThread()) {
throw new Error("the method must be called on the Event Dispatching thread");
}
if (runnable == null) return;
synchronized (priorityInvocationPending) {
priorityInvocationPending.set(true);
}
AWTAccessor.getEventQueueAccessor().createSecondaryLoop(
getSystemEventQueue(),
() -> blockingRunLoopCounter.get() > 0).enter();
try {
runnable.run();
} finally {
priorityInvocationPending.set(false);
}
}
/**
* Kicks an event over to the appropriate eventqueue and waits for it to
* finish To avoid deadlocking, we manually run the NSRunLoop while waiting
@@ -751,18 +766,11 @@ public final class LWCToolkit extends LWToolkit {
log.fine("invokeAndWait started: " + runnable);
}
if (isBlockingEventDispatchThread()) {
String msg = "invokeAndWait is discarded as the EventDispatch thread is currently blocked";
if (log.isLoggable(PlatformLogger.Level.FINE)) {
log.fine(msg, new Throwable());
log.fine(AWTThreading.getInstance(component).printEventDispatchThreadStackTrace().toString());
} else if (log.isLoggable(PlatformLogger.Level.INFO)) {
StackTraceElement[] stack = new Throwable().getStackTrace();
log.info(msg + ". Originated at " + stack[stack.length - 1]);
Thread dispatchThread = AWTThreading.getInstance(component).getEventDispatchThread();
log.info(dispatchThread.getName() + " at: " + dispatchThread.getStackTrace()[0].toString());
}
return;
boolean nonBlockingRunLoop;
synchronized (priorityInvocationPending) {
nonBlockingRunLoop = priorityInvocationPending.get();
if (!nonBlockingRunLoop) blockingRunLoopCounter.incrementAndGet();
}
AWTThreading.TrackedInvocationEvent invocationEvent =
@@ -783,22 +791,23 @@ public final class LWCToolkit extends LWToolkit {
((LWCToolkit)Toolkit.getDefaultToolkit()).getSystemEventQueueForInvokeAndWait().postEvent(invocationEvent);
}
if (DISPOSE_INVOCATION_ON_EDT_FREE) {
CompletableFuture<Void> eventDispatchThreadFreeFuture =
AWTThreading.getInstance(component).onEventDispatchThreadFree(() -> {
if (!invocationEvent.isCompleted(true)) {
// EventQueue is now empty but the posted InvocationEvent is still not dispatched,
// consider it lost then.
invocationEvent.dispose("InvocationEvent was lost");
}
});
invocationEvent.onDone(() -> eventDispatchThreadFreeFuture.cancel(false));
}
CompletableFuture<Void> eventDispatchThreadFreeFuture =
AWTThreading.getInstance(component).onEventDispatchThreadFree(() -> {
if (!invocationEvent.isDone()) {
// EventQueue is now empty but the posted InvocationEvent is still not dispatched,
// consider it lost then.
invocationEvent.dispose("InvocationEvent was lost");
}
});
if (!doAWTRunLoop(mediator, false, timeoutSeconds)) {
invocationEvent.onDone(() -> eventDispatchThreadFreeFuture.cancel(false));
if (!doAWTRunLoop(mediator, nonBlockingRunLoop, timeoutSeconds)) {
invocationEvent.dispose("InvocationEvent has timed out");
}
if (!nonBlockingRunLoop) blockingRunLoopCounter.decrementAndGet();
if (log.isLoggable(PlatformLogger.Level.FINE)) {
log.fine("invokeAndWait finished: " + runnable);
}
@@ -806,8 +815,6 @@ public final class LWCToolkit extends LWToolkit {
checkException(invocationEvent);
}
private static native boolean isBlockingEventDispatchThread();
public static void invokeLater(Runnable event, Component component)
throws InvocationTargetException {
Objects.requireNonNull(component, "Null component provided to invokeLater");
@@ -850,11 +857,6 @@ 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

@@ -69,9 +69,6 @@ static int* gsButtonEventNumber;
static NSTimeInterval gNextKeyEventTime;
static NSTimeInterval safeDelay;
#define KEY_CODE_COUNT 128
static CGEventFlags keyOwnFlags[KEY_CODE_COUNT];
static inline CGKeyCode GetCGKeyCode(jint javaKeyCode);
static void PostMouseEvent(const CGPoint point, CGMouseButton button,
@@ -110,20 +107,6 @@ static inline void autoDelay(BOOL isMove) {
gNextKeyEventTime = [[NSDate date] timeIntervalSinceReferenceDate] + safeDelay;
}
static void initKeyFlags() {
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStatePrivate);
for (CGKeyCode keyCode = 0; keyCode < KEY_CODE_COUNT; keyCode++) {
CGEventRef event = CGEventCreateKeyboardEvent(source, keyCode, true);
if (event != NULL) {
keyOwnFlags[keyCode] = CGEventGetFlags(event);
CFRelease(event);
}
}
if (source != NULL) {
CFRelease(source);
}
}
/*
* Class: sun_lwawt_macosx_CRobot
* Method: initRobot
@@ -172,8 +155,6 @@ Java_sun_lwawt_macosx_CRobot_initRobot
for (i = 0; i < gNumberOfButtons; ++i) {
gsButtonEventNumber[i] = ROBOT_EVENT_NUMBER_START;
}
initKeyFlags();
}];
}
}
@@ -302,20 +283,6 @@ Java_sun_lwawt_macosx_CRobot_mouseWheel
}];
}
// CGEventCreateKeyboardEvent incorrectly handles flags pertinent to non-modifier keys
// (e.g. F1-F12 keys always Fn flag set, while arrow keys always have Fn and NumPad flags set).
// Those flags are not cleared for following key presses automatically, so we need to do it ourselves.
// See JBR-4306 for details.
static void clearStickyFlags(CGEventRef event, CGKeyCode keyCode, CGEventFlags flagToCheck) {
if (keyCode < KEY_CODE_COUNT && (keyOwnFlags[keyCode] & flagToCheck) == 0) {
CGEventFlags flags = CGEventGetFlags(event);
CGEventFlags updatedFlags = flags & ~flagToCheck;
if (updatedFlags != flags) {
CGEventSetFlags(event, updatedFlags);
}
}
}
/*
* Class: sun_lwawt_macosx_CRobot
* Method: keyEvent
@@ -331,10 +298,6 @@ Java_sun_lwawt_macosx_CRobot_keyEvent
CGKeyCode keyCode = GetCGKeyCode(javaKeyCode);
CGEventRef event = CGEventCreateKeyboardEvent(source, keyCode, keyPressed);
if (event != NULL) {
// this assumes Robot isn't used to generate Fn key presses
clearStickyFlags(event, keyCode, kCGEventFlagMaskSecondaryFn);
// there is no NumPad key, so this won't hurt in any case
clearStickyFlags(event, keyCode, kCGEventFlagMaskNumericPad);
CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
}

View File

@@ -620,17 +620,6 @@ JNI_COCOA_EXIT(env);
return result;
}
/*
* Class: sun_lwawt_macosx_LWCToolkit
* Method: isBlockingEventDispatchThread
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_LWCToolkit_isBlockingEventDispatchThread
(JNIEnv *env, jclass clz)
{
return ThreadUtilities.blockingEventDispatchThread;
}
/*
* Class: sun_lwawt_macosx_LWCToolkit
* Method: stopAWTRunLoop
@@ -670,23 +659,6 @@ 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

@@ -127,13 +127,6 @@ do { \
__attribute__((visibility("default")))
@interface ThreadUtilities : NSObject { } /* Extend NSObject so can call performSelectorOnMainThread */
/*
* When a blocking performSelectorOnMainThread is executed from the EventDispatch thread,
* and the executed code triggers an opposite blocking a11y call (via LWCToolkit.invokeAndWait)
* this is a deadlock case, and then this property is used to discard LWCToolkit.invokeAndWait.
*/
@property (class, nonatomic, readonly) BOOL blockingEventDispatchThread;
+ (JNIEnv*)getJNIEnv;
+ (JNIEnv*)getJNIEnvUncached;
+ (void)detachCurrentThread;

View File

@@ -50,24 +50,6 @@ static inline void attachCurrentThread(void** env) {
@implementation ThreadUtilities
static BOOL _blockingEventDispatchThread = NO;
static long eventDispatchThreadPtr = (long)nil;
static BOOL isEventDispatchThread() {
return (long)[NSThread currentThread] == eventDispatchThreadPtr;
}
// The [blockingEventDispatchThread] property is readonly, so we implement a private setter
static void setBlockingEventDispatchThread(BOOL value) {
assert([NSThread isMainThread]);
_blockingEventDispatchThread = value;
}
+ (BOOL) blockingEventDispatchThread {
assert([NSThread isMainThread]);
return _blockingEventDispatchThread;
}
+ (void)initialize {
/* All the standard modes plus ours */
javaModes = [[NSArray alloc] initWithObjects:NSDefaultRunLoopMode,
@@ -100,10 +82,10 @@ AWT_ASSERT_APPKIT_THREAD;
}
/* This is needed because we can't directly pass a block to
* performSelectorOnMainThreadWaiting:..waitUntilDone:YES.. since it expects a selector
* performSelectorOnMainThreadWaiting .. since it expects a selector
*/
+ (void)invokeBlock:(void (^)())block {
block();
block();
}
/*
@@ -134,19 +116,7 @@ AWT_ASSERT_APPKIT_THREAD;
if ([NSThread isMainThread] && wait == YES) {
[target performSelector:aSelector withObject:arg];
} else {
if (wait && isEventDispatchThread()) {
void (^block)(void) = ^{
setBlockingEventDispatchThread(YES);
@try {
[target performSelector:aSelector withObject:arg];
} @finally {
setBlockingEventDispatchThread(NO);
}
};
[self performSelectorOnMainThread:@selector(invokeBlock:) withObject:block waitUntilDone:YES modes:javaModes];
} else {
[target performSelectorOnMainThread:aSelector withObject:arg waitUntilDone:wait modes:javaModes];
}
[target performSelectorOnMainThread:aSelector withObject:arg waitUntilDone:wait modes:javaModes];
}
}
@@ -173,16 +143,3 @@ JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_CThreading_isMainThread
return [NSThread isMainThread];
}
/*
* Class: sun_awt_AWTThreading
* Method: notifyEventDispatchThreadStartedNative
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_sun_awt_AWTThreading_notifyEventDispatchThreadStartedNative
(JNIEnv *env, jclass c)
{
@synchronized([ThreadUtilities class]) {
eventDispatchThreadPtr = (long)[NSThread currentThread];
}
}

View File

@@ -31,7 +31,6 @@ import java.awt.event.WindowEvent;
import java.util.ArrayList;
import sun.awt.AWTThreading;
import sun.util.logging.PlatformLogger;
import sun.awt.dnd.SunDragSourceContextPeer;
@@ -87,7 +86,6 @@ class EventDispatchThread extends Thread {
}
public void run() {
AWTThreading.getInstance(Thread.currentThread()).notifyEventDispatchThreadStarted();
try {
pumpEvents(new Conditional() {
public boolean evaluate() {
@@ -99,8 +97,6 @@ class EventDispatchThread extends Thread {
}
}
private static native void registerEventDispatchThread();
void pumpEvents(Conditional cond) {
pumpEvents(ANY_EVENT, cond);
}

View File

@@ -5,8 +5,6 @@ import sun.util.logging.PlatformLogger;
import java.awt.*;
import java.awt.event.InvocationEvent;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.ref.WeakReference;
import java.security.AccessController;
import java.security.PrivilegedAction;
@@ -16,7 +14,6 @@ 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.
@@ -78,25 +75,14 @@ public class AWTThreading {
}
/**
* 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).
* 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.
*/
public static void executeWaitToolkit(Consumer<Boolean> consumer) {
public static void executeWaitToolkit(Task runnable) {
boolean wait = EventQueue.isDispatchThread();
executeWaitToolkit(() -> {
consumer.accept(wait);
return null;
});
}
/**
* @see #executeWaitToolkit(Callable).
*/
public static void executeWaitToolkit(Runnable runnable) {
executeWaitToolkit(() -> {
runnable.run();
runnable.run(wait);
return null;
});
}
@@ -116,6 +102,12 @@ 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) {
@@ -254,8 +246,8 @@ public class AWTThreading {
private final Throwable throwable = new Throwable();
private final CompletableFuture<Void> futureResult = new CompletableFuture<>();
// dispatch or dispose has been started or already completed
private final AtomicBoolean isCompletionStarted = new AtomicBoolean(false);
// dispatched or disposed
private final AtomicBoolean isFinished = new AtomicBoolean(false);
static TrackedInvocationEvent create(Object source,
Runnable onDispatch,
@@ -273,7 +265,7 @@ public class AWTThreading {
TrackedInvocationEvent thisEvent = eventRef.get();
if (!thisEvent.isDispatched()) {
// If we're here - this {onDone} is being disposed.
thisEvent.completeIfNotYet(() ->
thisEvent.finishIfNotYet(() ->
// If we're here - this {onDone} is called by the outer AWTAccessor.getInvocationEventAccessor().dispose()
// which we do not control, so complete here.
thisEvent.futureResult.completeExceptionally(new Throwable("InvocationEvent was disposed"))
@@ -306,37 +298,28 @@ public class AWTThreading {
@Override
public void dispatch() {
completeIfNotYet(super::dispatch);
finishIfNotYet(super::dispatch);
futureResult.complete(null);
}
public void dispose(String reason) {
completeIfNotYet(() -> AWTAccessor.getInvocationEventAccessor().dispose(this));
finishIfNotYet(() -> AWTAccessor.getInvocationEventAccessor().dispose(this));
futureResult.completeExceptionally(new Throwable(reason));
}
private void completeIfNotYet(Runnable competeRunnable) {
if (!isCompletionStarted.getAndSet(true)) {
competeRunnable.run();
private void finishIfNotYet(Runnable finish) {
if (!isFinished.getAndSet(true)) {
finish.run();
}
}
/**
* Returns whether the event is dispatched or disposed.
*/
public boolean isCompleted() {
public boolean isDone() {
return futureResult.isDone();
}
/**
* Returns whether the event is dispatched or disposed.
* If {@code isCompletionInProgress} is true then also checks whether the event
* dispatching or disposal is in progress and if so returns true.
*/
public boolean isCompleted(boolean isCompletionInProgress) {
return isCompleted() || (isCompletionInProgress && isCompletionStarted.get());
}
/**
* Calls the runnable when it's done (immediately if it's done).
*/
@@ -374,19 +357,6 @@ public class AWTThreading {
return eventDispatchThread;
}
public StringWriter printEventDispatchThreadStackTrace() {
return printEventDispatchThreadStackTrace(new StringWriter());
}
public StringWriter printEventDispatchThreadStackTrace(StringWriter writer) {
assert writer != null;
var printer = new PrintWriter(writer);
Thread dispatchThread = getEventDispatchThread();
printer.println(dispatchThread.getName());
Arrays.asList(dispatchThread.getStackTrace()).forEach(frame -> printer.append("\tat ").println(frame));
return writer;
}
/**
* Sets {@code AWTThreading} instance factory.
* WARNING: for testing purpose.
@@ -395,15 +365,6 @@ public class AWTThreading {
theAWTThreadingFactory.set(factory);
}
/**
* Must be called on the EventDispatch thread.
*/
public void notifyEventDispatchThreadStarted() {
if (FontUtilities.isMacOSX) notifyEventDispatchThreadStartedNative();
}
private static native void notifyEventDispatchThreadStartedNative();
public void notifyEventDispatchThreadFree() {
List<CompletableFuture<Void>> notifiers = Collections.emptyList();
synchronized (eventDispatchThreadStateNotifiers) {
@@ -452,4 +413,8 @@ public class AWTThreading {
future.complete(null);
return future;
}
public interface Task {
void run(boolean wait);
}
}

View File

@@ -1,71 +0,0 @@
// Copyright 2000-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
import javax.swing.*;
import java.util.function.Consumer;
import static helper.ToolkitTestHelper.*;
import static helper.ToolkitTestHelper.TestCase.*;
/*
* @test
* @summary tests that CMenu/CMenuItem methods wrapped with {AWTThreading.executeWaitToolkit} are normally callable
* @requires (os.family == "mac")
* @modules java.desktop/sun.lwawt.macosx java.desktop/sun.awt
* @run main/othervm -Dapple.laf.useScreenMenuBar=true AWTThreadingCMenuTest
* @author Anton Tarasov
*/
public class AWTThreadingCMenuTest {
public static void main(String[] args) {
initTest(AWTThreadingCMenuTest.class);
testCase().
withCaption("populate Menu").
withRunnable(AWTThreadingCMenuTest::test1, true).
withExpectedInLog("discarded", false).
withCompletionTimeout(1).
run();
testCase().
withCaption("de-populate Menu").
withRunnable(AWTThreadingCMenuTest::test2, true).
withExpectedInLog("discarded", false).
withCompletionTimeout(1).
run();
System.out.println("Test PASSED");
}
@SuppressWarnings("deprecation")
private static void test1() {
var bar = new JMenuBar();
FRAME.setJMenuBar(bar);
var menu = new JMenu("Menu");
Consumer<String> addItem = text -> {
var item = new JMenuItem(text);
item.setLabel("label " + text);
menu.add(item);
menu.addSeparator();
};
addItem.accept("one");
addItem.accept("two");
addItem.accept("three");
bar.add(menu);
TEST_CASE_RESULT.complete(true);
}
private static void test2() {
var bar = FRAME.getJMenuBar();
var menu = bar.getMenu(0);
for (int i = 0; i < FRAME.getJMenuBar().getMenuCount(); i++) {
menu.remove(i);
}
TEST_CASE_RESULT.complete(true);
}
}

View File

@@ -1,134 +1,139 @@
// Copyright 2000-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
import java.awt.*;
import java.util.Arrays;
import java.util.concurrent.*;
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.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import sun.lwawt.macosx.CThreading;
import sun.lwawt.macosx.LWCToolkit;
import sun.awt.AWTThreading;
import static helper.ToolkitTestHelper.*;
import static helper.ToolkitTestHelper.TestCase.*;
/*
* @test
* @summary tests that AWTThreading can manage cross EDT/AppKit blocking invocation requests
* @summary tests that AWTThreading can manage a stream of cross EDT/AppKit invocation requests
* @requires (os.family == "mac")
* @modules java.desktop/sun.lwawt.macosx java.desktop/sun.awt
* @run main AWTThreadingTest
* @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
* @author Anton Tarasov
*/
@SuppressWarnings("ConstantConditions")
public class AWTThreadingTest {
static final int TIMEOUT_SECONDS = 1;
static final ReentrantLock LOCK = new ReentrantLock();
static final Condition COND = LOCK.newCondition();
static final CountDownLatch LATCH = new CountDownLatch(1);
static final AtomicInteger ITER_COUNTER = new AtomicInteger();
static final AtomicBoolean DUMP_STACK = new AtomicBoolean(false);
static JFrame frame;
static Thread thread;
static volatile Thread THREAD;
final static AtomicBoolean passed = new AtomicBoolean(true);
final static AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) {
DUMP_STACK.set(args.length > 0 && "dumpStack".equals(args[0]));
public static void main(String[] args) throws InterruptedException {
EventQueue.invokeLater(AWTThreadingTest::runGui);
initTest(AWTThreadingTest.class);
LATCH.await(5, TimeUnit.SECONDS);
testCase().
withCaption("certain threads superposition").
withRunnable(AWTThreadingTest::test, false).
run();
testCase().
withCaption("random threads superposition").
withRunnable(AWTThreadingTest::test, false).
run();
frame.dispose();
thread.interrupt();
if (!passed.get()) {
throw new RuntimeException("Test FAILED!");
}
System.out.println("Test PASSED");
}
static void test() {
ITER_COUNTER.set(0);
var timer = new TestTimer(TIMEOUT_SECONDS * 3, TimeUnit.SECONDS);
EventQueue.invokeLater(() -> startThread(() ->
TEST_CASE_RESULT.isDone() ||
timer.hasFinished()));
waitTestCaseCompletion(TIMEOUT_SECONDS * 4);
tryRun(THREAD::join);
System.out.println(ITER_COUNTER + " iterations 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();
}
});
frame.setVisible(true);
}
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);
static void startThread() {
thread = new Thread(() -> {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
break;
}
//
// 1. Blocking invocation from AppKit to EDT
// 1. Execute invokeAndWait() from AppKit to EDT
//
CThreading.executeOnAppKit(() -> {
// We're on AppKit, wait for the 2nd invocation to be on the AWTThreading-pool thread.
if (TEST_CASE_NUM == 1) await(point_1, TIMEOUT_SECONDS);
tryRun(() -> LWCToolkit.invokeAndWait(() -> {
// We're being dispatched on EDT.
if (TEST_CASE_NUM == 1) point_2.countDown();
// Wait for the 2nd invocation to be executed on AppKit.
if (TEST_CASE_NUM == 1) await(point_3, TIMEOUT_SECONDS);
}, FRAME));
invocations.countDown();
try {
LWCToolkit.invokeAndWait(counter::incrementAndGet, Window.getWindows()[0]);
} catch (Exception e) {
fail(e);
}
});
//
// 2. Blocking invocation from EDT to AppKit
// 2. Execute invokeAndBlock() from EDT to AppKit
//
EventQueue.invokeLater(() -> AWTThreading.executeWaitToolkit(() -> {
// We're on the AWTThreading-pool thread.
if (TEST_CASE_NUM == 1) point_1.countDown();
EventQueue.invokeLater(() -> {
passed.set(false);
// Wait for the 1st invocation to start NSRunLoop and be dispatched
if (TEST_CASE_NUM == 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();
Boolean success = AWTThreading.executeWaitToolkit(() -> {
try {
return CThreading.executeOnAppKit(() -> Boolean.TRUE);
} catch (Throwable e) {
fail(e);
}
// We're being executed on AppKit.
if (TEST_CASE_NUM == 1) point_3.countDown();
return null;
});
System.out.println("Success: " + counter.get() + ": " + success);
invocations.countDown();
}));
passed.set(Boolean.TRUE.equals(success));
await(invocations, TIMEOUT_SECONDS * 2);
} // while
if (passed.get()) {
lock(COND::signal);
}
else {
fail(null);
}
});
TEST_CASE_RESULT.complete(true);
lock(COND::await);
}
});
THREAD.setDaemon(true);
THREAD.start();
thread.setDaemon(true);
thread.start();
}
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("\tat " + frame));
});
System.out.println("\n\n");
static void lock(MyRunnable runnable) {
LOCK.lock();
try {
try {
runnable.run();
} catch (Exception e) {
e.printStackTrace();
}
} finally {
LOCK.unlock();
}
}
interface MyRunnable {
void run() throws Exception;
}
static void fail(Throwable e) {
if (e != null) e.printStackTrace();
passed.set(false);
LATCH.countDown();
}
}

View File

@@ -1,35 +1,38 @@
// Copyright 2000-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
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.*;
import sun.awt.AWTAccessor;
import sun.awt.AWTThreading;
import sun.lwawt.macosx.CThreading;
import sun.lwawt.macosx.LWCToolkit;
import static helper.ToolkitTestHelper.*;
import static helper.ToolkitTestHelper.TestCase.*;
/*
* @test
* @summary Tests different scenarios for LWCToolkit.invokeAndWait().
* @requires (os.family == "mac")
* @modules java.desktop/sun.lwawt.macosx java.desktop/sun.awt
* @run main/othervm -Dsun.lwawt.macosx.LWCToolkit.invokeAndWait.disposeOnEDTFree=true LWCToolkitInvokeAndWaitTest
* @run main/othervm -Dlog.level.FINER=true -Dsun.lwawt.macosx.LWCToolkit.invokeAndWait.disposeOnEDTFree=true LWCToolkitInvokeAndWaitTest
* @run main LWCToolkitInvokeAndWaitTest
* @run main/othervm -DAWTThreading.level.FINER=true LWCToolkitInvokeAndWaitTest
* @author Anton Tarasov
*/
@SuppressWarnings("ConstantConditions")
public class LWCToolkitInvokeAndWaitTest {
// This property is used in {CAccessibility}
static final int INVOKE_TIMEOUT_SECONDS = Integer.getInteger("sun.lwawt.macosx.CAccessibility.invokeTimeoutSeconds", 1);
static final Runnable CONSUME_DISPATCHING = () -> TEST_CASE_RESULT.completeExceptionally(new Throwable("Unexpected dispatching!"));
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 Runnable CONSUME_DISPATCHING = () -> FUTURE.completeExceptionally(new Throwable("Unexpected dispatching!"));
static {
AWTThreading.setAWTThreadingFactory(edt -> new AWTThreading(edt) {
@@ -38,7 +41,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
tryRun(EDT_FAST_FREE_LATCH::await);
trycatch(() -> EDT_FAST_FREE_LATCH.await());
EDT_FAST_FREE_LATCH = null;
}
@@ -59,69 +62,101 @@ public class LWCToolkitInvokeAndWaitTest {
}
public static void main(String[] args) {
initTest(LWCToolkitInvokeAndWaitTest.class);
MAIN_THREAD = Thread.currentThread();
trycatch(() -> {
Logger log = LogManager.getLogManager().getLogger(AWTThreading.class.getName());
log.setUseParentHandlers(false);
log.addHandler(LOG_HANDLER);
if (Boolean.getBoolean("AWTThreading.level.FINER")) {
log.setLevel(Level.FINER);
}
});
Consumer<InvocationEvent> noop = e -> {};
testCase().
withCaption("InvocationEvent is normally dispatched").
withRunnable(() -> test1(noop, () -> System.out.println("I'm dispatched")), true).
withCompletionTimeout(INVOKE_TIMEOUT_SECONDS * 2).
run();
try {
trycatch(() -> EventQueue.invokeAndWait(LWCToolkitInvokeAndWaitTest::runGui));
testCase().
withCaption("InvocationEvent is lost").
withRunnable(() -> test1(noop, CONSUME_DISPATCHING), true).
withCompletionTimeout(INVOKE_TIMEOUT_SECONDS * 2).
withExpectedInLog("lost", true).
run();
test("InvocationEvent is normally dispatched",
"",
noop,
() -> System.out.println("I'm dispatched"));
EDT_FAST_FREE_LATCH = new CountDownLatch(2);
testCase().
withCaption("InvocationEvent is lost (EDT becomes fast free)").
withRunnable(() -> test1(invocationEvent -> EDT_FAST_FREE_LATCH.countDown(), CONSUME_DISPATCHING), true).
withCompletionTimeout(INVOKE_TIMEOUT_SECONDS * 2).
withExpectedInLog("lost", true).
run();
test("InvocationEvent is lost",
"lost",
noop,
CONSUME_DISPATCHING);
testCase().
withCaption("InvocationEvent is disposed").
withRunnable(() -> test1(invocationEvent -> AWTAccessor.getInvocationEventAccessor().dispose(invocationEvent), CONSUME_DISPATCHING), true).
withCompletionTimeout(INVOKE_TIMEOUT_SECONDS * 2).
withExpectedInLog("disposed", true).
run();
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);
testCase().
withCaption("InvocationEvent is timed out (delayed before dispatching)").
withRunnable(() -> test1(invocationEvent -> sleep(INVOKE_TIMEOUT_SECONDS * 4), CONSUME_DISPATCHING), true).
withCompletionTimeout(INVOKE_TIMEOUT_SECONDS * 2).
withExpectedInLog("timed out", true).
run();
test("InvocationEvent is disposed",
"disposed",
invocationEvent -> AWTAccessor.getInvocationEventAccessor().dispose(invocationEvent),
CONSUME_DISPATCHING);
testCase().
withCaption("InvocationEvent is timed out (delayed during dispatching)").
withRunnable(() -> test1(noop, () -> sleep(INVOKE_TIMEOUT_SECONDS * 4)), true).
withCompletionTimeout(INVOKE_TIMEOUT_SECONDS * 2).
withExpectedInLog("timed out", true).
run();
test("InvocationEvent is timed out (delayed before dispatching)",
"timed out",
invocationEvent -> sleep(INVOKE_TIMEOUT_SECONDS * 4),
CONSUME_DISPATCHING);
testCase().
withCaption("invokeAndWait is discarded").
withRunnable(LWCToolkitInvokeAndWaitTest::test2, true).
withCompletionTimeout(INVOKE_TIMEOUT_SECONDS * 2).
withExpectedInLog("discarded", true).
run();
testCase().
withCaption("invokeAndWait is passed").
withRunnable(LWCToolkitInvokeAndWaitTest::test3, true).
withCompletionTimeout(INVOKE_TIMEOUT_SECONDS * 2).
run();
test("InvocationEvent is timed out (delayed during dispatching)",
"timed out",
noop,
() -> sleep(INVOKE_TIMEOUT_SECONDS * 4));
} finally {
FRAME.dispose();
}
System.out.println("Test PASSED");
}
static void test1(Consumer<InvocationEvent> onBeforeDispatching, Runnable onDispatching) {
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,
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(() -> {
if (!FUTURE.get(INVOKE_TIMEOUT_SECONDS * 2L, TimeUnit.SECONDS)) {
throw new RuntimeException("Test FAILED! (negative result)");
}
});
// let AppKit and EDT print all the logging
var latch = new CountDownLatch(1);
CThreading.executeOnAppKit(latch::countDown);
trycatch(latch::await);
trycatch(() -> EventQueue.invokeAndWait(() -> {}));
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) {
Toolkit.getDefaultToolkit().getSystemEventQueue().push(new EventQueue() {
@Override
protected void dispatchEvent(AWTEvent event) {
@@ -140,54 +175,55 @@ public class LWCToolkitInvokeAndWaitTest {
super.dispatchEvent(event);
}
});
CThreading.executeOnAppKit(() -> tryRun(() -> {
CThreading.executeOnAppKit(() -> trycatch(() -> {
//
// Post an invocation from AppKit.
//
LWCToolkit.invokeAndWait(onDispatching, FRAME, false, INVOKE_TIMEOUT_SECONDS);
TEST_CASE_RESULT.complete(true);
FUTURE.complete(true);
}));
}
static void test2() {
EventQueue.invokeLater(() ->
//
// Blocking EDT.
//
LWCToolkit.performOnMainThreadAndWait(() -> {
//
// The invocation from AppKit should be discarded.
//
tryRun(() -> LWCToolkit.invokeAndWait(EMPTY_RUNNABLE, FRAME, false, INVOKE_TIMEOUT_SECONDS * 4));
TEST_CASE_RESULT.complete(true);
}));
static void sleep(int seconds) {
trycatch(() -> Thread.sleep(seconds * 1000L));
}
static void test3() {
var point = new CountDownLatch(1);
EventQueue.invokeLater(() -> {
// We're on EDT, wait for the second invocation to perform on AppKit.
await(point, INVOKE_TIMEOUT_SECONDS * 2);
// This should be dispatched in the RunLoop started by LWCToolkit.invokeAndWait from the second invocation.
LWCToolkit.performOnMainThreadAndWait(() -> TEST_CASE_RESULT.complete(true));
});
LWCToolkit.performOnMainThreadAndWait(() -> {
// Notify we're on AppKit.
point.countDown();
// The LWCToolkit.invokeAndWait call starts a native RunLoop.
tryRun(() -> LWCToolkit.invokeAndWait(EMPTY_RUNNABLE, FRAME));
});
}
static void check(String expectedInLog) {
tryRun(() -> {
if (!TEST_CASE_RESULT.get(INVOKE_TIMEOUT_SECONDS * 2L, TimeUnit.SECONDS)) {
throw new RuntimeException("Test FAILED! (negative result)");
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;
}
static class TestLogHandler extends StreamHandler {
public StringBuilder buffer = new StringBuilder();
public TestLogHandler() {
// Use System.out to merge with the test printing.
super(System.out, new SimpleFormatter());
setLevel(Level.ALL);
}
@Override
public void publish(LogRecord record) {
buffer.append(record.getMessage());
super.publish(record);
flush();
}
public boolean testContains(String str) {
boolean contains = buffer.toString().contains(str);
buffer.setLength(0);
return contains;
}
}
}

View File

@@ -1,239 +0,0 @@
// Copyright 2000-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package helper;
import sun.awt.AWTThreading;
import sun.lwawt.macosx.CThreading;
import sun.lwawt.macosx.LWCToolkit;
import javax.swing.*;
import javax.swing.Timer;
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.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.logging.*;
@SuppressWarnings("ConstantConditions")
public class ToolkitTestHelper {
public static final Runnable EMPTY_RUNNABLE = () -> {};
public static final TestLogHandler LOG_HANDLER = new TestLogHandler();
public static volatile CompletableFuture<Boolean> TEST_CASE_RESULT;
public static volatile int TEST_CASE_NUM;
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_NUM + ")");
}
};
public static void initTest(Class<?> testClass) {
MAIN_THREAD = Thread.currentThread();
assert MAIN_THREAD.getName().toLowerCase().contains("main");
loop(); // init the event threads
tryRun(() -> {
Consumer<Class<?>> setLog = cls -> {
Logger log = LogManager.getLogManager().getLogger(cls.getName());
log.setUseParentHandlers(false);
log.addHandler(LOG_HANDLER);
if (Boolean.getBoolean("log.level.FINER")) {
log.setLevel(Level.FINER);
}
};
setLog.accept(AWTThreading.class);
setLog.accept(LWCToolkit.class);
});
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 waitTestCaseCompletion(int seconds) {
tryRun(() -> {
if (!TEST_CASE_RESULT.get(seconds, TimeUnit.SECONDS)) {
throw new RuntimeException("Test FAILED! (negative result)");
}
});
loop(); // wait for the logging to be printed
}
public static class TestCase {
volatile String caption = "";
volatile Runnable testRunnable = EMPTY_RUNNABLE;
volatile int completionTimeoutSeconds = -1;
volatile String expectedInLog = "";
volatile boolean expectedIncludedInLog = true;
public static TestCase testCase() {
return new TestCase();
}
public TestCase withCaption(String caption) {
this.caption = caption;
return this;
}
public TestCase withRunnable(Runnable testRunnable, boolean invokeOnEdt) {
this.testRunnable = invokeOnEdt ? () -> EventQueue.invokeLater(testRunnable) : testRunnable;
return this;
}
public TestCase withCompletionTimeout(int seconds) {
this.completionTimeoutSeconds = seconds;
return this;
}
public TestCase withExpectedInLog(String expectedInLog, boolean included) {
this.expectedInLog = expectedInLog;
this.expectedIncludedInLog = included;
return this;
}
public void run() {
TEST_CASE_RESULT = new CompletableFuture<>();
TEST_CASE_RESULT.whenComplete((r, e) -> Optional.of(e).ifPresent(Throwable::printStackTrace));
//noinspection NonAtomicOperationOnVolatileField
String prefix = "(" + (++TEST_CASE_NUM) + ")";
System.out.println("\n" + prefix + " TEST: " + caption);
UPDATE_LABEL.run();
testRunnable.run();
if (completionTimeoutSeconds >= 0) {
waitTestCaseCompletion(completionTimeoutSeconds);
if (expectedIncludedInLog && !LOG_HANDLER.testContains(expectedInLog)) {
throw new RuntimeException("Test FAILED! (not found in the log: \"" + expectedInLog + "\")");
} else if (!expectedIncludedInLog && LOG_HANDLER.testContains(expectedInLog)) {
throw new RuntimeException("Test FAILED! (found in the log: \"" + expectedInLog + "\")");
}
}
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 {
TEST_CASE_RESULT.completeExceptionally(e);
}
}
return defValue;
}
public static void await(CountDownLatch latch, int seconds) {
if (!tryCall(() -> latch.await(seconds, TimeUnit.SECONDS), false)) {
TEST_CASE_RESULT.completeExceptionally(new Throwable("Awaiting has timed out"));
}
}
public static void sleep(int seconds) {
tryRun(() -> Thread.sleep(seconds * 1000L));
}
private static void loop() {
tryRun(() -> EventQueue.invokeAndWait(EMPTY_RUNNABLE));
var latch = new CountDownLatch(1);
CThreading.executeOnAppKit(latch::countDown);
tryRun(latch::await);
}
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;
}
}
public static class TestLogHandler extends StreamHandler {
public StringBuilder buffer = new StringBuilder();
public TestLogHandler() {
// Use System.out to merge with the test printing.
super(System.out, new SimpleFormatter());
setLevel(Level.ALL);
}
@Override
public void publish(LogRecord record) {
buffer.append(record.getMessage());
super.publish(record);
flush();
}
public boolean testContains(String str) {
boolean contains = buffer.toString().contains(str);
buffer.setLength(0);
return contains;
}
}
}

View File

@@ -524,7 +524,6 @@ java/awt/image/VolatileImage/BitmaskVolatileImage.java 8133102 linux-all
java/awt/SplashScreen/MultiResolutionSplash/MultiResolutionSplashTest.java 8134231 windows-all,linux-all,macosx-all
java/awt/SplashScreen/MultiResolutionSplash/unix/UnixMultiResolutionSplashTest.java 8203004 linux-all
java/awt/Robot/AcceptExtraMouseButtons/AcceptExtraMouseButtons.java 7107528 linux-all,macosx-all
java/awt/Mouse/GetMousePositionTest/GetMousePositionWithPopup.java 8282232 windows-all
java/awt/Mouse/MouseDragEvent/MouseDraggedTest.java 8080676 linux-all
java/awt/Mouse/MouseModifiersUnitTest/MouseModifiersInKeyEvent.java 8157147 linux-all,windows-all,macosx-all
java/awt/Mouse/TitleBarDoubleClick/TitleBarDoubleClick.java 8148041 linux-all
@@ -593,7 +592,6 @@ javax/management/monitor/DerivedGaugeMonitorTest.java 8042211 generic-al
# jdk_io
java/io/pathNames/GeneralWin32.java 8180264 windows-all
java/io/File/createTempFile/SpecialTempFile.java 8274122 windows11
############################################################################