/*
* 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 static java.lang.Thread.currentThread;
import static java.util.concurrent.TimeUnit.SECONDS;
import junit.framework.Assert;
import junit.framework.TestCase;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
/**
* Unit test for {@link AbstractService}.
*
* @author Jesse Wilson
*/
public class AbstractServiceTest extends TestCase {
private Thread executionThread;
private Throwable thrownByExecutionThread;
public void testNoOpServiceStartStop() {
NoOpService service = new NoOpService();
Assert.assertEquals(Service.State.NEW, service.state());
assertFalse(service.isRunning());
assertFalse(service.running);
service.start();
assertEquals(Service.State.RUNNING, service.state());
assertTrue(service.isRunning());
assertTrue(service.running);
service.stop();
assertEquals(Service.State.TERMINATED, service.state());
assertFalse(service.isRunning());
assertFalse(service.running);
}
public void testNoOpServiceStartAndWaitStopAndWait() throws Exception {
NoOpService service = new NoOpService();
service.start().get();
assertEquals(Service.State.RUNNING, service.state());
service.stop().get();
assertEquals(Service.State.TERMINATED, service.state());
}
public void testNoOpServiceStartStopIdempotence() throws Exception {
NoOpService service = new NoOpService();
service.start();
service.start();
assertEquals(Service.State.RUNNING, service.state());
service.stop();
service.stop();
assertEquals(Service.State.TERMINATED, service.state());
}
public void testNoOpServiceStartStopIdempotenceAfterWait() throws Exception {
NoOpService service = new NoOpService();
service.start().get();
service.start();
assertEquals(Service.State.RUNNING, service.state());
service.stop().get();
service.stop();
assertEquals(Service.State.TERMINATED, service.state());
}
public void testNoOpServiceStartStopIdempotenceDoubleWait() throws Exception {
NoOpService service = new NoOpService();
service.start().get();
service.start().get();
assertEquals(Service.State.RUNNING, service.state());
service.stop().get();
service.stop().get();
assertEquals(Service.State.TERMINATED, service.state());
}
public void testNoOpServiceStartStopAndWaitUninterruptible()
throws Exception {
NoOpService service = new NoOpService();
currentThread().interrupt();
try {
service.startAndWait();
assertEquals(Service.State.RUNNING, service.state());
service.stopAndWait();
assertEquals(Service.State.TERMINATED, service.state());
assertTrue(currentThread().isInterrupted());
} finally {
Thread.interrupted(); // clear interrupt for future tests
}
}
private static class NoOpService extends AbstractService {
boolean running = false;
@Override protected void doStart() {
assertFalse(running);
running = true;
notifyStarted();
}
@Override protected void doStop() {
assertTrue(running);
running = false;
notifyStopped();
}
}
public void testManualServiceStartStop() {
ManualSwitchedService service = new ManualSwitchedService();
service.start();
assertEquals(Service.State.STARTING, service.state());
assertFalse(service.isRunning());
assertTrue(service.doStartCalled);
service.notifyStarted(); // usually this would be invoked by another thread
assertEquals(Service.State.RUNNING, service.state());
assertTrue(service.isRunning());
service.stop();
assertEquals(Service.State.STOPPING, service.state());
assertFalse(service.isRunning());
assertTrue(service.doStopCalled);
service.notifyStopped(); // usually this would be invoked by another thread
assertEquals(Service.State.TERMINATED, service.state());
assertFalse(service.isRunning());
}
public void testManualServiceStopWhileStarting() {
ManualSwitchedService service = new ManualSwitchedService();
service.start();
assertEquals(Service.State.STARTING, service.state());
assertFalse(service.isRunning());
assertTrue(service.doStartCalled);
service.stop();
assertEquals(Service.State.STOPPING, service.state());
assertFalse(service.isRunning());
assertFalse(service.doStopCalled);
service.notifyStarted();
assertEquals(Service.State.STOPPING, service.state());
assertFalse(service.isRunning());
assertTrue(service.doStopCalled);
service.notifyStopped();
assertEquals(Service.State.TERMINATED, service.state());
assertFalse(service.isRunning());
}
public void testManualServiceUnrequestedStop() {
ManualSwitchedService service = new ManualSwitchedService();
service.start();
service.notifyStarted();
assertEquals(Service.State.RUNNING, service.state());
assertTrue(service.isRunning());
assertFalse(service.doStopCalled);
service.notifyStopped();
assertEquals(Service.State.TERMINATED, service.state());
assertFalse(service.isRunning());
assertFalse(service.doStopCalled);
}
/**
* The user of this service should call {@link #notifyStarted} and {@link
* #notifyStopped} after calling {@link #start} and {@link #stop}.
*/
private static class ManualSwitchedService extends AbstractService {
boolean doStartCalled = false;
boolean doStopCalled = false;
@Override protected void doStart() {
assertFalse(doStartCalled);
doStartCalled = true;
}
@Override protected void doStop() {
assertFalse(doStopCalled);
doStopCalled = true;
}
}
public void testThreadedServiceStartAndWaitStopAndWait() throws Throwable {
ThreadedService service = new ThreadedService();
service.start().get();
assertEquals(Service.State.RUNNING, service.state());
service.awaitRunChecks();
service.stop().get();
assertEquals(Service.State.TERMINATED, service.state());
throwIfSet(thrownByExecutionThread);
}
public void testThreadedServiceStartStopIdempotence() throws Throwable {
ThreadedService service = new ThreadedService();
service.start();
service.start().get();
assertEquals(Service.State.RUNNING, service.state());
service.awaitRunChecks();
service.stop();
service.stop().get();
assertEquals(Service.State.TERMINATED, service.state());
throwIfSet(thrownByExecutionThread);
}
public void testThreadedServiceStartStopIdempotenceAfterWait()
throws Throwable {
ThreadedService service = new ThreadedService();
service.start().get();
service.start();
assertEquals(Service.State.RUNNING, service.state());
service.awaitRunChecks();
service.stop().get();
service.stop();
assertEquals(Service.State.TERMINATED, service.state());
executionThread.join();
throwIfSet(thrownByExecutionThread);
}
public void testThreadedServiceStartStopIdempotenceDoubleWait()
throws Throwable {
ThreadedService service = new ThreadedService();
service.start().get();
service.start().get();
assertEquals(Service.State.RUNNING, service.state());
service.awaitRunChecks();
service.stop().get();
service.stop().get();
assertEquals(Service.State.TERMINATED, service.state());
throwIfSet(thrownByExecutionThread);
}
private class ThreadedService extends AbstractService {
final CountDownLatch hasConfirmedIsRunning = new CountDownLatch(1);
/*
* The main test thread tries to stop() the service shortly after
* confirming that it is running. Meanwhile, the service itself is trying
* to confirm that it is running. If the main thread's stop() call happens
* before it has the chance, the test will fail. To avoid this, the main
* thread calls this method, which waits until the service has performed
* its own "running" check.
*/
void awaitRunChecks() throws InterruptedException {
assertTrue("Service thread hasn't finished its checks. "
+ "Exception status (possibly stale): " + thrownByExecutionThread,
hasConfirmedIsRunning.await(10, SECONDS));
}
@Override protected void doStart() {
assertEquals(State.STARTING, state());
invokeOnExecutionThreadForTest(new Runnable() {
@Override public void run() {
assertEquals(State.STARTING, state());
notifyStarted();
assertEquals(State.RUNNING, state());
hasConfirmedIsRunning.countDown();
}
});
}
@Override protected void doStop() {
assertEquals(State.STOPPING, state());
invokeOnExecutionThreadForTest(new Runnable() {
@Override public void run() {
assertEquals(State.STOPPING, state());
notifyStopped();
assertEquals(State.TERMINATED, state());
}
});
}
}
private void invokeOnExecutionThreadForTest(Runnable runnable) {
executionThread = new Thread(runnable);
executionThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable e) {
thrownByExecutionThread = e;
}
});
executionThread.start();
}
private static void throwIfSet(Throwable t) throws Throwable {
if (t != null) {
throw t;
}
}
public void testStopUnstartedService() throws Exception {
NoOpService service = new NoOpService();
Future<Service.State> stopResult = service.stop();
assertEquals(Service.State.TERMINATED, service.state());
assertEquals(Service.State.TERMINATED, stopResult.get());
Future<Service.State> startResult = service.start();
assertEquals(Service.State.TERMINATED, service.state());
assertEquals(Service.State.TERMINATED, startResult.get());
}
public void testThrowingServiceStartAndWait() throws Exception {
StartThrowingService service = new StartThrowingService();
try {
service.startAndWait();
fail();
} catch (UncheckedExecutionException e) {
assertEquals(EXCEPTION, e.getCause());
}
}
public void testThrowingServiceStopAndWait_stopThrowing() throws Exception {
StopThrowingService service = new StopThrowingService();
service.startAndWait();
try {
service.stopAndWait();
fail();
} catch (UncheckedExecutionException e) {
assertEquals(EXCEPTION, e.getCause());
}
}
public void testThrowingServiceStopAndWait_runThrowing() throws Exception {
RunThrowingService service = new RunThrowingService();
service.startAndWait();
try {
service.stopAndWait();
fail();
} catch (UncheckedExecutionException e) {
assertEquals(EXCEPTION, e.getCause().getCause());
}
}
private static class StartThrowingService extends AbstractService {
@Override protected void doStart() {
notifyFailed(EXCEPTION);
}
@Override protected void doStop() {
fail();
}
}
private static class RunThrowingService extends AbstractService {
@Override protected void doStart() {
notifyStarted();
notifyFailed(EXCEPTION);
}
@Override protected void doStop() {
fail();
}
}
private static class StopThrowingService extends AbstractService {
@Override protected void doStart() {
notifyStarted();
}
@Override protected void doStop() {
notifyFailed(EXCEPTION);
}
}
private static final Exception EXCEPTION = new Exception();
}