/*******************************************************************************
* Copyright (c) 2007, 2009 Innoopract Informationssysteme GmbH.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Innoopract Informationssysteme GmbH - initial API and implementation
* EclipseSource - ongoing development
******************************************************************************/
package org.eclipse.rwt.internal.lifecycle;
import java.util.ArrayList;
import java.util.List;
import junit.framework.TestCase;
import org.eclipse.rwt.*;
import org.eclipse.rwt.internal.service.*;
import org.eclipse.rwt.lifecycle.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.internal.widgets.IDisplayAdapter;
import org.eclipse.swt.widgets.Display;
public class UICallBackManager_Test extends TestCase {
private static final int SLEEP_TIME = 200;
private static final int TIMER_EXEC_DELAY = 1000;
private static final String RUN_ASYNC_EXEC = "run async exec|";
private static final String RUN_TIMER_EXEC = "timerExecCode|";
private static final Runnable EMPTY_RUNNABLE = new Runnable() {
public void run() {
}
};
private static String log = "";
private Display display;
protected void setUp() throws Exception {
Fixture.setUp();
log = "";
display = new Display();
}
protected void tearDown() throws Exception {
Fixture.tearDown();
}
public void testWaitFor() throws InterruptedException {
final Throwable[] uiCallBackServiceHandlerThrowable = { null };
final ServiceContext context[] = { ContextProvider.getContext() };
Thread thread = new Thread( new Runnable() {
public void run() {
ContextProvider.setContext( context[ 0 ] );
Fixture.fakeResponseWriter();
UICallBackServiceHandler uiCallBackServiceHandler
= new UICallBackServiceHandler();
try {
UICallBackManager.getInstance().setActive( true );
uiCallBackServiceHandler.service();
} catch( Throwable thr ) {
uiCallBackServiceHandlerThrowable[ 0 ] = thr;
}
}
} );
thread.start();
Thread.sleep( SLEEP_TIME );
if( uiCallBackServiceHandlerThrowable[ 0 ] != null ) {
uiCallBackServiceHandlerThrowable[ 0 ].printStackTrace();
}
assertNull( uiCallBackServiceHandlerThrowable[ 0 ] );
assertTrue( UICallBackManager.getInstance().isCallBackRequestBlocked() );
UICallBackManager.getInstance().sendUICallBack();
Thread.sleep( SLEEP_TIME );
assertFalse( UICallBackManager.getInstance().isCallBackRequestBlocked() );
Thread.sleep( SLEEP_TIME );
assertFalse( thread.isAlive() );
}
public void testWaitOnUIThread() throws Exception {
final Throwable[] uiCallBackServiceHandlerThrowable = { null };
ServiceContext context = ContextProvider.getContext();
simulateUiCallBackThread( uiCallBackServiceHandlerThrowable, context );
assertNull( uiCallBackServiceHandlerThrowable[ 0 ] );
display.wake();
assertTrue( UICallBackManager.getInstance().isCallBackRequestBlocked() );
UICallBackManager.getInstance().sendImmediately();
}
public void testWaitOnBackgroundThread() throws Exception {
final Throwable[] uiCallBackServiceHandlerThrowable = { null };
ServiceContext context = ContextProvider.getContext();
simulateUiCallBackThread( uiCallBackServiceHandlerThrowable, context );
assertNull( uiCallBackServiceHandlerThrowable[ 0 ] );
assertTrue( UICallBackManager.getInstance().isCallBackRequestBlocked() );
Thread thread = new Thread( new Runnable() {
public void run() {
display.wake();
}
} );
thread.start();
thread.join();
Thread.sleep( SLEEP_TIME );
assertFalse( UICallBackManager.getInstance().isCallBackRequestBlocked() );
}
public void testAsyncExecWhileLifeCycleIsRunning() {
fakeRequestParam( display );
Fixture.fakePhase( PhaseId.READ_DATA );
simulateBackgroundAdditionDuringLifeCycle( display );
Fixture.fakePhase( PhaseId.PROCESS_ACTION );
display.readAndDispatch();
assertEquals( 1, getDisplayAdapter().getAsyncRunnablesCount() );
Fixture.executeLifeCycleFromServerThread();
assertEquals( RUN_ASYNC_EXEC, log );
assertFalse( UICallBackManager.getInstance().isCallBackRequestBlocked() );
}
public void testAsyncExecWithBackgroundAndLifeCycleRunnables()
throws Exception
{
// test unblocking in case of background addition of runnables
simulateBackgroundAddition( ContextProvider.getContext() );
// test runnables execution during lifecycle with interlocked additions
fakeRequestParam( display );
Fixture.fakePhase( PhaseId.READ_DATA );
simulateBackgroundAdditionDuringLifeCycle( display );
Fixture.executeLifeCycleFromServerThread();
assertFalse( UICallBackManager.getInstance().isCallBackRequestBlocked() );
assertEquals( RUN_ASYNC_EXEC + RUN_ASYNC_EXEC + RUN_ASYNC_EXEC, log );
}
public void testCallBackRequestBlocking() throws Exception {
final Throwable[] uiCallBackServiceHandlerThrowable = { null };
ServiceContext context = ContextProvider.getContext();
simulateUiCallBackThread( uiCallBackServiceHandlerThrowable, context );
assertNull( uiCallBackServiceHandlerThrowable[ 0 ] );
assertTrue( UICallBackManager.getInstance().isCallBackRequestBlocked() );
}
public void testCallBackRequestReleasing() throws Exception {
final Throwable[] uiCallBackServiceHandlerThrowable = { null };
ServiceContext context = ContextProvider.getContext();
Thread uiCallBackThread
= simulateUiCallBackThread( uiCallBackServiceHandlerThrowable, context );
simulateBackgroundAddition( context );
assertFalse( UICallBackManager.getInstance().isCallBackRequestBlocked() );
assertFalse( uiCallBackThread.isAlive() );
assertEquals( "", log );
}
public void testAsyncExec() throws InterruptedException {
final Throwable[] uiCallBackServiceHandlerThrowable = { null };
ServiceContext context = ContextProvider.getContext();
// test runnables addition while no uiCallBack thread is not blocked
Thread uiCallBackThread
= simulateUiCallBackThread( uiCallBackServiceHandlerThrowable, context );
UICallBackManager.getInstance().notifyUIThreadEnd();
simulateBackgroundAddition( context );
assertNull( uiCallBackServiceHandlerThrowable[ 0 ] );
assertFalse( UICallBackManager.getInstance().isCallBackRequestBlocked() );
// since no UI thread is running and
// runnables available do not block
assertFalse( uiCallBackThread.isAlive() );
UICallBackManager.getInstance().notifyUIThreadStart();
// test blocking of incomming uiCallBack thread while UI thread is running
fakeRequestParam( display );
simulateUICallBackThreadLockDuringLifeCycle(
context,
uiCallBackServiceHandlerThrowable );
Fixture.executeLifeCycleFromServerThread();
if( uiCallBackServiceHandlerThrowable[ 0 ] != null ) {
uiCallBackServiceHandlerThrowable[ 0 ].printStackTrace();
}
assertNull( uiCallBackServiceHandlerThrowable[ 0 ] );
assertTrue( UICallBackManager.getInstance().isCallBackRequestBlocked() );
assertEquals( RUN_ASYNC_EXEC + RUN_ASYNC_EXEC, log );
}
public void testExceptionInAsyncExec() {
Fixture.fakePhase( PhaseId.PROCESS_ACTION );
final RuntimeException exception
= new RuntimeException( "bad things happen" );
Runnable runnable = new Runnable() {
public void run() {
throw exception;
}
};
display.asyncExec( runnable );
try {
display.readAndDispatch();
String msg
= "Exception that occurs in an asynExec runnable must be wrapped "
+ "in an SWTException";
fail( msg );
} catch( SWTException e ) {
assertEquals( SWT.ERROR_FAILED_EXEC, e.code );
assertSame( exception, e.throwable );
}
}
public void testTimerExec() throws Exception {
Fixture.fakePhase( PhaseId.PROCESS_ACTION );
Runnable runnable = new Runnable() {
public void run() {
log += RUN_TIMER_EXEC;
}
};
display.timerExec( TIMER_EXEC_DELAY, runnable );
assertFalse( display.readAndDispatch() );
assertEquals( "", log.toString() );
Thread.sleep( TIMER_EXEC_DELAY + 50 );
display.readAndDispatch();
assertEquals( RUN_TIMER_EXEC, log.toString() );
}
public void testTimerExecWithIllegalArgument() {
try {
display.timerExec( 1, null );
fail( "timerExec: runnable must not be null" );
} catch( IllegalArgumentException expected ) {
}
}
public void testTimerExecFromBackgroundThread() throws Exception {
final Throwable[] exceptionInTimerExec = { null };
Runnable runnable = new Runnable() {
public void run() {
try {
display.timerExec( 5000, EMPTY_RUNNABLE );
} catch( Throwable t ) {
exceptionInTimerExec[ 0 ] = t;
}
}
};
Thread thread = new Thread( runnable );
thread.setDaemon( true );
thread.start();
thread.join();
assertNotNull( exceptionInTimerExec[ 0 ] );
assertTrue( exceptionInTimerExec[ 0 ] instanceof SWTException );
SWTException swtException = ( SWTException )exceptionInTimerExec[ 0 ];
assertEquals( swtException.code, SWT.ERROR_THREAD_INVALID_ACCESS );
}
// Ensure that runnables that were added via addTimer but should be executed
// in the future are *not* executed on session shutdown
public void testNoTimerExecAfterSessionShutdown() throws Exception {
Runnable runnable = new Runnable() {
public void run() {
log += RUN_TIMER_EXEC;
}
};
display.timerExec( 200, runnable );
display.dispose();
Thread.sleep( 200 );
assertEquals( "", log.toString() );
}
public void testRemoveAddedTimerExec() throws Exception {
Runnable runnable = new Runnable() {
public void run() {
log += RUN_TIMER_EXEC;
}
};
display.timerExec( 200, runnable );
display.timerExec( -1, runnable );
Thread.sleep( 2000 );
assertEquals( 0, getDisplayAdapter().getAsyncRunnablesCount() );
assertEquals( "", log );
}
public void testRemoveNonExistingTimerExec() {
display.timerExec( -1, EMPTY_RUNNABLE );
// must not cause any exception
}
// This test ensures that addSync doesn't cause deadlocks
public void testSyncExecBlock() throws Exception {
Fixture.fakePhase( PhaseId.PROCESS_ACTION );
final ServiceContext context = ContextProvider.getContext();
// the code in bgRunnable simulates a bg-thread that calls Display#addSync
Runnable bgRunnable = new Runnable() {
public void run() {
ContextProvider.setContext( context );
Fixture.fakeResponseWriter();
Runnable doNothing = new Runnable() {
public void run() {
}
};
display.syncExec( doNothing );
}
};
// simulate a lot "parallel" bg-threads to provoke multi-threading problems
List bgThreads = new ArrayList();
for( int i = 0; i < 200; i++ ) {
Thread bgThread = new Thread( bgRunnable, "Test-Bg-Thread " + i );
bgThread.setDaemon( true );
bgThread.start();
display.readAndDispatch();
bgThreads.add( bgThread );
}
// wait (hopefully long enough) until all bg-threads have done their work
// (i.e. called addSync) and make sure all sync-runnables get executed
Thread.sleep( 20 );
while( display.readAndDispatch() ) {
Thread.sleep( 20 );
}
// wait for all bgThreads to terminate
for( int i = 0; i < bgThreads.size(); i++ ) {
Thread bgThread = ( Thread )bgThreads.get( i );
bgThread.join();
display.readAndDispatch();
}
IDisplayAdapter adapter = getDisplayAdapter();
assertEquals( 0, adapter.getAsyncRunnablesCount() );
}
// This test ensures that SyncRunnable releases the blocked thread in case of
// an exception
public void testAddSyncWithExceptionInRunnable() throws Exception {
Fixture.fakePhase( PhaseId.PROCESS_ACTION );
final ServiceContext context = ContextProvider.getContext();
final SWTException[] exceptionInBgThread = { null };
// the code in bgRunnable simulates a bg-thread that calls Display#addSync
// and causes an exception in the runnable
Runnable bgRunnable = new Runnable() {
public void run() {
ContextProvider.setContext( context );
Fixture.fakeResponseWriter();
Runnable causeException = new Runnable() {
public void run() {
throw new RuntimeException( "Exception in sync-runnable" );
}
};
try {
display.syncExec( causeException );
} catch( SWTException e ) {
exceptionInBgThread[ 0 ] = e;
}
}
};
Thread bgThread = new Thread( bgRunnable );
bgThread.setDaemon( true );
bgThread.start();
Thread.sleep( SLEEP_TIME );
try {
display.readAndDispatch();
fail( "Exception from causeException-runnable must end up here" );
} catch( SWTException e ) {
// expected
}
Thread.sleep( SLEEP_TIME );
assertNotNull( exceptionInBgThread[ 0 ] );
assertFalse( bgThread.isAlive() );
}
private Thread simulateUiCallBackThread(
final Throwable[] uiCallBackServiceHandlerThrowable,
final ServiceContext context )
throws InterruptedException
{
Thread uiCallBackThread = new Thread( new Runnable() {
public void run() {
ContextProvider.setContext( context );
Fixture.fakeResponseWriter();
TestResponse response = ( TestResponse )context.getResponse();
response.setOutputStream( new TestServletOutputStream() );
UICallBackServiceHandler uiCallBackServiceHandler = new UICallBackServiceHandler();
try {
UICallBackManager.getInstance().setActive( true );
uiCallBackServiceHandler.service();
} catch( Throwable thr ) {
uiCallBackServiceHandlerThrowable[ 0 ] = thr;
}
}
} );
uiCallBackThread.start();
Thread.sleep( SLEEP_TIME );
return uiCallBackThread;
}
private void simulateBackgroundAddition( final ServiceContext context )
throws InterruptedException
{
Thread backgroundThread = new Thread( new Runnable() {
public void run() {
ContextProvider.setContext( context );
display.asyncExec( new Runnable() {
public void run() {
log += RUN_ASYNC_EXEC;
}
} );
display.asyncExec( new Runnable() {
public void run() {
log += RUN_ASYNC_EXEC;
}
} );
}
} );
backgroundThread.start();
Thread.sleep( SLEEP_TIME );
}
private static void simulateBackgroundAdditionDuringLifeCycle(
final Display display )
{
ProcessActionRunner.add( new Runnable() {
public void run() {
Thread thread = new Thread( new Runnable() {
public void run() {
UICallBack.runNonUIThreadWithFakeContext( display, new Runnable() {
public void run() {
display.asyncExec( new Runnable() {
public void run() {
log += RUN_ASYNC_EXEC;
}
} );
}
} );
}
} );
thread.start();
try {
thread.join();
} catch( InterruptedException e ) {
e.printStackTrace();
}
}
} );
}
private static void simulateUICallBackThreadLockDuringLifeCycle(
final ServiceContext context,
final Throwable[] uiCallBackServiceHandlerThrowable )
{
final RWTLifeCycle lifeCycle
= ( RWTLifeCycle )LifeCycleFactory.getLifeCycle();
lifeCycle.addPhaseListener( new PhaseListener() {
private static final long serialVersionUID = 1L;
public void afterPhase( final PhaseEvent event ) {
Thread uiCallBackThread = new Thread( new Runnable() {
public void run() {
ContextProvider.setContext( context );
Fixture.fakeResponseWriter();
UICallBackServiceHandler uiCallBackServiceHandler
= new UICallBackServiceHandler();
try {
uiCallBackServiceHandler.service();
} catch( Throwable thr ) {
uiCallBackServiceHandlerThrowable[ 0 ] = thr;
}
}
} );
uiCallBackThread.start();
try {
Thread.sleep( SLEEP_TIME );
} catch( InterruptedException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
}
lifeCycle.removePhaseListener( this );
}
public void beforePhase( final PhaseEvent event ) {
}
public PhaseId getPhaseId() {
return PhaseId.READ_DATA;
}
} );
}
private static void fakeRequestParam( final Display display ) {
Fixture.fakeResponseWriter();
String id = "org.eclipse.swt.display";
ContextProvider.getSession().setAttribute( id, display );
String displayId = DisplayUtil.getAdapter( display ).getId();
Fixture.fakeRequestParam( RequestParams.UIROOT, displayId );
}
private IDisplayAdapter getDisplayAdapter() {
return ( IDisplayAdapter )display.getAdapter( IDisplayAdapter.class );
}
}