/*******************************************************************************
* Copyright (c) 2014 - 2017 RĂ¼diger Herrmann
* 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:
* RĂ¼diger Herrmann - initial API and implementation
******************************************************************************/
package com.codeaffine.eclipse.swt.util;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import com.codeaffine.eclipse.swt.test.util.SWTIgnoreConditions.NonWindowsPlatform;
import com.codeaffine.test.util.junit.ConditionalIgnoreRule;
import com.codeaffine.test.util.junit.ConditionalIgnoreRule.ConditionalIgnore;
import com.codeaffine.test.util.util.concurrent.RunInThread;
import com.codeaffine.test.util.util.concurrent.RunInThreadRule;
@ConditionalIgnore(condition=NonWindowsPlatform.class)
public class UIThreadSynchronizerTest {
@Rule
public final ConditionalIgnoreRule ignoreRule = new ConditionalIgnoreRule();
@Rule
public final RunInThreadRule runInThreadRule = new RunInThreadRule();
private Display display;
private Widget widget;
private Runnable runnable;
private UIThreadSynchronizer synchronizer;
@RunInThread
@Test
public void testSyncExecRunsCode() {
synchronizer.syncExec( widget, runnable );
verify( runnable ).run();
}
@RunInThread
@Test
public void testSyncExecAfterWidgetIsDisposedDoesNotRunCode() throws InterruptedException {
widget.dispose();
runInThread( new Runnable() {
@Override
public void run() {
synchronizer.syncExec( widget, runnable );
}
} );
verify( runnable, never() ).run();
}
@RunInThread
@Test
public void testSyncExecWithDisposedDisplayDoesNotRunCode() {
display.dispose();
synchronizer.asyncExec( widget, runnable );
verify( runnable, never() ).run();
}
@RunInThread
@Test
public void testAsyncExecRunsCode() {
synchronizer.asyncExec( widget, runnable );
flushPendingEvents();
verify( runnable ).run();
}
@RunInThread
@Test
public void testAsyncExecRunsCodeFromBackgroundThread() throws InterruptedException {
runInThread( new Runnable() {
@Override
public void run() {
synchronizer.asyncExec( widget, runnable );
}
} );
flushPendingEvents();
verify( runnable ).run();
}
@RunInThread
@Test
public void testAsyncExecAfterWidgetIsDisposedDoesNotRunCode() throws InterruptedException {
runInThread( new Runnable() {
@Override
public void run() {
synchronizer.asyncExec( widget, runnable );
}
} );
widget.dispose();
flushPendingEvents();
verify( runnable, never() ).run();
}
@RunInThread
@Test
public void testAsyncExecWithDisposedDisplayDoesNotRunCode() {
display.dispose();
synchronizer.asyncExec( widget, runnable );
flushPendingEvents();
verify( runnable, never() ).run();
}
@RunInThread
@Test(expected=SWTException.class)
public void testAsyncExecPropagatesWidgetDisposedExceptionInRunnable() {
doThrow( new SWTException( SWT.ERROR_WIDGET_DISPOSED ) ).when( runnable ).run();
synchronizer.asyncExec( widget, runnable );
flushPendingEvents();
}
@Before
public void setUp() {
display = new Display();
widget = new Shell( display );
runnable = mock( Runnable.class );
synchronizer = new UIThreadSynchronizer();
}
@After
public void tearDown() {
display.dispose();
}
private void flushPendingEvents() {
while( !display.isDisposed() && display.readAndDispatch() ) {}
}
private static void runInThread( final Runnable runnable ) throws InterruptedException {
ExceptionGuard exceptionGuard = new ExceptionGuard( runnable );
Thread thread = new Thread( exceptionGuard );
thread.setDaemon( true );
thread.start();
thread.join();
exceptionGuard.handleException();
}
private static class ExceptionGuard implements Runnable {
private final Runnable runnable;
private volatile Throwable caughtException;
ExceptionGuard( Runnable runnable ) {
this.runnable = runnable;
}
@Override
public void run() {
try {
runnable.run();
} catch( Throwable thr ) {
caughtException = thr;
}
}
void handleException() {
if( caughtException != null ) {
throw new RuntimeException( "Caught exception in thread", caughtException );
}
}
}
}