1/* 2 * Copyright (C) 2009 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.google.common.util.concurrent; 18 19import com.google.common.base.Throwables; 20 21import junit.framework.TestCase; 22 23import java.lang.Thread.UncaughtExceptionHandler; 24import java.util.concurrent.CountDownLatch; 25import java.util.concurrent.ExecutionException; 26import java.util.concurrent.Executor; 27import java.util.concurrent.Future; 28import java.util.concurrent.TimeUnit; 29import java.util.concurrent.TimeoutException; 30 31/** 32 * Unit test for {@link AbstractExecutionThreadService}. 33 * 34 * @author Jesse Wilson 35 */ 36public class AbstractExecutionThreadServiceTest extends TestCase { 37 38 private final CountDownLatch enterRun = new CountDownLatch(1); 39 private final CountDownLatch exitRun = new CountDownLatch(1); 40 41 private Thread executionThread; 42 private Throwable thrownByExecutionThread; 43 private final Executor executor = new Executor() { 44 @Override 45 public void execute(Runnable command) { 46 executionThread = new Thread(command); 47 executionThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { 48 @Override 49 public void uncaughtException(Thread thread, Throwable e) { 50 thrownByExecutionThread = e; 51 } 52 }); 53 executionThread.start(); 54 } 55 }; 56 57 public void testServiceStartStop() throws Exception { 58 WaitOnRunService service = new WaitOnRunService(); 59 assertFalse(service.startUpCalled); 60 61 service.start().get(); 62 assertTrue(service.startUpCalled); 63 assertEquals(Service.State.RUNNING, service.state()); 64 65 enterRun.await(); // to avoid stopping the service until run() is invoked 66 67 service.stop().get(); 68 assertTrue(service.shutDownCalled); 69 assertEquals(Service.State.TERMINATED, service.state()); 70 executionThread.join(); 71 assertNull(thrownByExecutionThread); 72 } 73 74 public void testServiceStartStopIdempotence() throws Exception { 75 WaitOnRunService service = new WaitOnRunService(); 76 77 service.start(); 78 service.start(); 79 service.startAndWait(); 80 assertEquals(Service.State.RUNNING, service.state()); 81 service.startAndWait(); 82 assertEquals(Service.State.RUNNING, service.state()); 83 84 enterRun.await(); // to avoid stopping the service until run() is invoked 85 86 service.stop(); 87 service.stop(); 88 service.stopAndWait(); 89 assertEquals(Service.State.TERMINATED, service.state()); 90 service.stopAndWait(); 91 assertEquals(Service.State.TERMINATED, service.state()); 92 93 assertEquals(Service.State.RUNNING, service.start().get()); 94 assertEquals(Service.State.RUNNING, service.startAndWait()); 95 assertEquals(Service.State.TERMINATED, service.stop().get()); 96 assertEquals(Service.State.TERMINATED, service.stopAndWait()); 97 98 executionThread.join(); 99 assertNull(thrownByExecutionThread); 100 } 101 102 public void testServiceExitingOnItsOwn() throws Exception { 103 WaitOnRunService service = new WaitOnRunService(); 104 service.expectedShutdownState = Service.State.RUNNING; 105 106 service.start().get(); 107 assertTrue(service.startUpCalled); 108 assertEquals(Service.State.RUNNING, service.state()); 109 110 exitRun.countDown(); // the service will exit voluntarily 111 executionThread.join(); 112 113 assertTrue(service.shutDownCalled); 114 assertEquals(Service.State.TERMINATED, service.state()); 115 assertNull(thrownByExecutionThread); 116 117 service.stop().get(); // no-op 118 assertEquals(Service.State.TERMINATED, service.state()); 119 assertTrue(service.shutDownCalled); 120 } 121 122 private class WaitOnRunService extends AbstractExecutionThreadService { 123 private boolean startUpCalled = false; 124 private boolean runCalled = false; 125 private boolean shutDownCalled = false; 126 private State expectedShutdownState = State.STOPPING; 127 128 @Override protected void startUp() { 129 assertFalse(startUpCalled); 130 assertFalse(runCalled); 131 assertFalse(shutDownCalled); 132 startUpCalled = true; 133 assertEquals(State.STARTING, state()); 134 } 135 136 @Override protected void run() { 137 assertTrue(startUpCalled); 138 assertFalse(runCalled); 139 assertFalse(shutDownCalled); 140 runCalled = true; 141 assertEquals(State.RUNNING, state()); 142 143 enterRun.countDown(); 144 try { 145 exitRun.await(); 146 } catch (InterruptedException e) { 147 throw Throwables.propagate(e); 148 } 149 } 150 151 @Override protected void shutDown() { 152 assertTrue(startUpCalled); 153 assertTrue(runCalled); 154 assertFalse(shutDownCalled); 155 shutDownCalled = true; 156 assertEquals(expectedShutdownState, state()); 157 } 158 159 @Override protected void triggerShutdown() { 160 exitRun.countDown(); 161 } 162 163 @Override protected Executor executor() { 164 return executor; 165 } 166 } 167 168 public void testServiceThrowOnStartUp() throws Exception { 169 ThrowOnStartUpService service = new ThrowOnStartUpService(); 170 assertFalse(service.startUpCalled); 171 172 Future<Service.State> startupFuture = service.start(); 173 try { 174 startupFuture.get(); 175 fail(); 176 } catch (ExecutionException expected) { 177 assertEquals("kaboom!", expected.getCause().getMessage()); 178 } 179 executionThread.join(); 180 181 assertTrue(service.startUpCalled); 182 assertEquals(Service.State.FAILED, service.state()); 183 assertTrue(thrownByExecutionThread.getMessage().equals("kaboom!")); 184 } 185 186 private class ThrowOnStartUpService extends AbstractExecutionThreadService { 187 private boolean startUpCalled = false; 188 189 @Override protected void startUp() { 190 startUpCalled = true; 191 throw new UnsupportedOperationException("kaboom!"); 192 } 193 194 @Override protected void run() { 195 throw new AssertionError("run() should not be called"); 196 } 197 198 @Override protected Executor executor() { 199 return executor; 200 } 201 } 202 203 public void testServiceThrowOnRun() throws Exception { 204 ThrowOnRunService service = new ThrowOnRunService(); 205 206 service.start().get(); 207 208 executionThread.join(); 209 assertTrue(service.shutDownCalled); 210 assertEquals(Service.State.FAILED, service.state()); 211 assertEquals("kaboom!", thrownByExecutionThread.getMessage()); 212 } 213 214 public void testServiceThrowOnRunAndThenAgainOnShutDown() throws Exception { 215 ThrowOnRunService service = new ThrowOnRunService(); 216 service.throwOnShutDown = true; 217 218 service.start().get(); 219 executionThread.join(); 220 221 assertTrue(service.shutDownCalled); 222 assertEquals(Service.State.FAILED, service.state()); 223 assertEquals("kaboom!", thrownByExecutionThread.getMessage()); 224 } 225 226 private class ThrowOnRunService extends AbstractExecutionThreadService { 227 private boolean shutDownCalled = false; 228 private boolean throwOnShutDown = false; 229 230 @Override protected void run() { 231 throw new UnsupportedOperationException("kaboom!"); 232 } 233 234 @Override protected void shutDown() { 235 shutDownCalled = true; 236 if (throwOnShutDown) { 237 throw new UnsupportedOperationException("double kaboom!"); 238 } 239 } 240 241 @Override protected Executor executor() { 242 return executor; 243 } 244 } 245 246 public void testServiceThrowOnShutDown() throws Exception { 247 ThrowOnShutDown service = new ThrowOnShutDown(); 248 249 service.start().get(); 250 assertEquals(Service.State.RUNNING, service.state()); 251 252 service.stop(); 253 enterRun.countDown(); 254 executionThread.join(); 255 256 assertEquals(Service.State.FAILED, service.state()); 257 assertEquals("kaboom!", thrownByExecutionThread.getMessage()); 258 } 259 260 private class ThrowOnShutDown extends AbstractExecutionThreadService { 261 @Override protected void run() { 262 try { 263 enterRun.await(); 264 } catch (InterruptedException e) { 265 throw Throwables.propagate(e); 266 } 267 } 268 269 @Override protected void shutDown() { 270 throw new UnsupportedOperationException("kaboom!"); 271 } 272 273 @Override protected Executor executor() { 274 return executor; 275 } 276 } 277 278 public void testServiceTimeoutOnStartUp() throws Exception { 279 TimeoutOnStartUp service = new TimeoutOnStartUp(); 280 281 try { 282 service.start().get(1, TimeUnit.MILLISECONDS); 283 fail(); 284 } catch (TimeoutException e) { 285 assertTrue(e.getMessage().contains(Service.State.STARTING.toString())); 286 } 287 } 288 289 private class TimeoutOnStartUp extends AbstractExecutionThreadService { 290 @Override protected Executor executor() { 291 return new Executor() { 292 @Override public void execute(Runnable command) { 293 } 294 }; 295 } 296 297 @Override 298 protected void run() throws Exception { 299 } 300 } 301 302} 303