/*
* Copyright (C) 2009 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.util.concurrent;
import com.google.common.base.Throwables;
import junit.framework.TestCase;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Unit test for {@link AbstractExecutionThreadService}.
*
* @author Jesse Wilson
*/
public class AbstractExecutionThreadServiceTest extends TestCase {
private final CountDownLatch enterRun = new CountDownLatch(1);
private final CountDownLatch exitRun = new CountDownLatch(1);
private Thread executionThread;
private Throwable thrownByExecutionThread;
private final Executor executor = new Executor() {
@Override
public void execute(Runnable command) {
executionThread = new Thread(command);
executionThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable e) {
thrownByExecutionThread = e;
}
});
executionThread.start();
}
};
public void testServiceStartStop() throws Exception {
WaitOnRunService service = new WaitOnRunService();
assertFalse(service.startUpCalled);
service.start().get();
assertTrue(service.startUpCalled);
assertEquals(Service.State.RUNNING, service.state());
enterRun.await(); // to avoid stopping the service until run() is invoked
service.stop().get();
assertTrue(service.shutDownCalled);
assertEquals(Service.State.TERMINATED, service.state());
executionThread.join();
assertNull(thrownByExecutionThread);
}
public void testServiceStartStopIdempotence() throws Exception {
WaitOnRunService service = new WaitOnRunService();
service.start();
service.start();
service.startAndWait();
assertEquals(Service.State.RUNNING, service.state());
service.startAndWait();
assertEquals(Service.State.RUNNING, service.state());
enterRun.await(); // to avoid stopping the service until run() is invoked
service.stop();
service.stop();
service.stopAndWait();
assertEquals(Service.State.TERMINATED, service.state());
service.stopAndWait();
assertEquals(Service.State.TERMINATED, service.state());
assertEquals(Service.State.RUNNING, service.start().get());
assertEquals(Service.State.RUNNING, service.startAndWait());
assertEquals(Service.State.TERMINATED, service.stop().get());
assertEquals(Service.State.TERMINATED, service.stopAndWait());
executionThread.join();
assertNull(thrownByExecutionThread);
}
public void testServiceExitingOnItsOwn() throws Exception {
WaitOnRunService service = new WaitOnRunService();
service.expectedShutdownState = Service.State.RUNNING;
service.start().get();
assertTrue(service.startUpCalled);
assertEquals(Service.State.RUNNING, service.state());
exitRun.countDown(); // the service will exit voluntarily
executionThread.join();
assertTrue(service.shutDownCalled);
assertEquals(Service.State.TERMINATED, service.state());
assertNull(thrownByExecutionThread);
service.stop().get(); // no-op
assertEquals(Service.State.TERMINATED, service.state());
assertTrue(service.shutDownCalled);
}
private class WaitOnRunService extends AbstractExecutionThreadService {
private boolean startUpCalled = false;
private boolean runCalled = false;
private boolean shutDownCalled = false;
private State expectedShutdownState = State.STOPPING;
@Override protected void startUp() {
assertFalse(startUpCalled);
assertFalse(runCalled);
assertFalse(shutDownCalled);
startUpCalled = true;
assertEquals(State.STARTING, state());
}
@Override protected void run() {
assertTrue(startUpCalled);
assertFalse(runCalled);
assertFalse(shutDownCalled);
runCalled = true;
assertEquals(State.RUNNING, state());
enterRun.countDown();
try {
exitRun.await();
} catch (InterruptedException e) {
throw Throwables.propagate(e);
}
}
@Override protected void shutDown() {
assertTrue(startUpCalled);
assertTrue(runCalled);
assertFalse(shutDownCalled);
shutDownCalled = true;
assertEquals(expectedShutdownState, state());
}
@Override protected void triggerShutdown() {
exitRun.countDown();
}
@Override protected Executor executor() {
return executor;
}
}
public void testServiceThrowOnStartUp() throws Exception {
ThrowOnStartUpService service = new ThrowOnStartUpService();
assertFalse(service.startUpCalled);
Future<Service.State> startupFuture = service.start();
try {
startupFuture.get();
fail();
} catch (ExecutionException expected) {
assertEquals("kaboom!", expected.getCause().getMessage());
}
executionThread.join();
assertTrue(service.startUpCalled);
assertEquals(Service.State.FAILED, service.state());
assertTrue(thrownByExecutionThread.getMessage().equals("kaboom!"));
}
private class ThrowOnStartUpService extends AbstractExecutionThreadService {
private boolean startUpCalled = false;
@Override protected void startUp() {
startUpCalled = true;
throw new UnsupportedOperationException("kaboom!");
}
@Override protected void run() {
throw new AssertionError("run() should not be called");
}
@Override protected Executor executor() {
return executor;
}
}
public void testServiceThrowOnRun() throws Exception {
ThrowOnRunService service = new ThrowOnRunService();
service.start().get();
executionThread.join();
assertTrue(service.shutDownCalled);
assertEquals(Service.State.FAILED, service.state());
assertEquals("kaboom!", thrownByExecutionThread.getMessage());
}
public void testServiceThrowOnRunAndThenAgainOnShutDown() throws Exception {
ThrowOnRunService service = new ThrowOnRunService();
service.throwOnShutDown = true;
service.start().get();
executionThread.join();
assertTrue(service.shutDownCalled);
assertEquals(Service.State.FAILED, service.state());
assertEquals("kaboom!", thrownByExecutionThread.getMessage());
}
private class ThrowOnRunService extends AbstractExecutionThreadService {
private boolean shutDownCalled = false;
private boolean throwOnShutDown = false;
@Override protected void run() {
throw new UnsupportedOperationException("kaboom!");
}
@Override protected void shutDown() {
shutDownCalled = true;
if (throwOnShutDown) {
throw new UnsupportedOperationException("double kaboom!");
}
}
@Override protected Executor executor() {
return executor;
}
}
public void testServiceThrowOnShutDown() throws Exception {
ThrowOnShutDown service = new ThrowOnShutDown();
service.start().get();
assertEquals(Service.State.RUNNING, service.state());
service.stop();
enterRun.countDown();
executionThread.join();
assertEquals(Service.State.FAILED, service.state());
assertEquals("kaboom!", thrownByExecutionThread.getMessage());
}
private class ThrowOnShutDown extends AbstractExecutionThreadService {
@Override protected void run() {
try {
enterRun.await();
} catch (InterruptedException e) {
throw Throwables.propagate(e);
}
}
@Override protected void shutDown() {
throw new UnsupportedOperationException("kaboom!");
}
@Override protected Executor executor() {
return executor;
}
}
public void testServiceTimeoutOnStartUp() throws Exception {
TimeoutOnStartUp service = new TimeoutOnStartUp();
try {
service.start().get(1, TimeUnit.MILLISECONDS);
fail();
} catch (TimeoutException e) {
assertTrue(e.getMessage().contains(Service.State.STARTING.toString()));
}
}
private class TimeoutOnStartUp extends AbstractExecutionThreadService {
@Override protected Executor executor() {
return new Executor() {
@Override public void execute(Runnable command) {
}
};
}
@Override
protected void run() throws Exception {
}
}
}