/* * 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 { } } }