blob: da18f72cff07c51e4a0544ef363075e1c71587ce [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011-2019 EclipseSource Muenchen GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* edgar - initial API and implementation
* Christian W. Damus - bug 545686
******************************************************************************/
package org.eclipse.emf.ecp.view.table.ui.swt.test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.emf.ecp.view.internal.table.swt.RunnableManager;
import org.eclipse.emf.ecp.view.internal.table.swt.RunnableManager.BackgroundStage;
import org.eclipse.swt.widgets.Display;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
public class RunnableManagerTest {
private static final int NR_RUNNABLES = 1000;
private static final ScheduledExecutorService POOL = Executors.newScheduledThreadPool(1);
private static final ScheduledExecutorService ASYNC_POOL = Executors.newScheduledThreadPool(1);
private static final Display DISPLAY = mock(Display.class);
private Runnable createRunnable(final CountDownLatch latch,
final AtomicInteger counter) {
return new Runnable() {
@Override
public void run() {
counter.getAndIncrement();
latch.countDown();
}
};
}
@BeforeClass
public static void beforeClass() {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
ASYNC_POOL.submit(
Runnable.class.cast(invocation.getArguments()[0]));
return null;
}
}).when(DISPLAY).asyncExec(any(Runnable.class));
}
@AfterClass
public static void afterClass() {
ASYNC_POOL.shutdown();
POOL.shutdown();
}
@Test
public void managedRunnable() throws InterruptedException {
// setup
final AtomicInteger asyncCounter = new AtomicInteger(0);
final AtomicInteger counter = new AtomicInteger(0);
final CountDownLatch latch = new CountDownLatch(NR_RUNNABLES);
final RunnableManager runnableManager = new RunnableManager(DISPLAY);
// act
for (int x = 0; x < NR_RUNNABLES; x++) {
runnableManager.executeAsync(createRunnable(latch, asyncCounter));
POOL.submit(createRunnable(latch, counter));
}
latch.await();
// assert
// we expect more runnables to be executed via the fixed pool since
// the RunnableManager only lets a single Runnable to be executed
assertTrue(counter.intValue() > asyncCounter.intValue());
}
@Test
public void backgroundStage() throws InterruptedException {
final AtomicInteger stageCount = new AtomicInteger();
final Stage stage = new Stage(stageCount::incrementAndGet);
final RunnableManager runnableManager = new RunnableManager(DISPLAY);
runnableManager.executeAsync(stage);
stage.join();
assertThat("Multi-step execution not completed", stageCount.get(), greaterThanOrEqualTo(3));
}
@Test
public void waitForIdle() throws InterruptedException {
final AtomicInteger countdown = new AtomicInteger(Stage.STAGE_COUNT);
final Runnable callback = () -> {
try {
Thread.sleep(100L);
} catch (final InterruptedException e) {
fail("Call-back interrupted");
}
countdown.decrementAndGet();
};
final Stage stage = new Stage(callback);
final RunnableManager runnableManager = new RunnableManager(DISPLAY);
runnableManager.executeAsync(stage);
runnableManager.waitForIdle();
assertThat("Wait returned early", countdown.get(), lessThanOrEqualTo(0));
}
@Test
public void waitForIdle_time() throws InterruptedException {
final AtomicInteger countdown = new AtomicInteger(Stage.STAGE_COUNT);
final Runnable callback = () -> {
try {
Thread.sleep(100L);
} catch (final InterruptedException e) {
fail("Call-back interrupted");
}
countdown.decrementAndGet();
};
final Stage stage = new Stage(callback);
final RunnableManager runnableManager = new RunnableManager(DISPLAY);
runnableManager.executeAsync(stage);
assertThat("Timed out", runnableManager.waitForIdle(5L, TimeUnit.SECONDS), is(true));
assertThat("Wait returned early", countdown.get(), lessThanOrEqualTo(0));
}
@Test
public void waitForIdle_timeout() throws InterruptedException {
final AtomicInteger countdown = new AtomicInteger(Stage.STAGE_COUNT);
final Runnable callback = () -> {
try {
Thread.sleep(1000L);
} catch (final InterruptedException e) {
fail("Call-back interrupted");
}
countdown.decrementAndGet();
};
final Stage stage = new Stage(callback);
final RunnableManager runnableManager = new RunnableManager(DISPLAY);
runnableManager.executeAsync(stage);
assertThat("Not timed out", runnableManager.waitForIdle(1L, TimeUnit.SECONDS), is(false));
assertThat("Task completed", countdown.get(), greaterThan(0));
}
//
// Nested types
//
private static class Stage implements Runnable, BackgroundStage {
static final int STAGE_COUNT = 3;
private final Runnable callback;
private int backgroundStages = STAGE_COUNT - 1;
private Runnable next;
private final CountDownLatch done = new CountDownLatch(1);
Stage(Runnable callback) {
super();
this.callback = callback;
}
@Override
public void run() {
// This is a stage
try {
callback.run();
} finally {
// This is a background stage
backgroundStages--;
if (backgroundStages <= 0) {
// Last stage is foreground
next = () -> {
try {
callback.run();
} finally {
done.countDown();
}
};
} else {
// Next stage is background
next = this;
}
}
}
@Override
public Runnable getNextStage() {
return next;
}
void join() throws InterruptedException {
done.await();
}
}
}