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 17 package com.google.common.util.concurrent; 18 19 import com.google.common.testing.TearDown; 20 import com.google.common.testing.TearDownStack; 21 22 import junit.framework.TestCase; 23 24 import java.lang.Thread.UncaughtExceptionHandler; 25 import java.util.concurrent.CountDownLatch; 26 import java.util.concurrent.Executor; 27 import java.util.concurrent.ExecutorService; 28 import java.util.concurrent.Executors; 29 import java.util.concurrent.TimeUnit; 30 import java.util.concurrent.TimeoutException; 31 32 /** 33 * Unit test for {@link AbstractExecutionThreadService}. 34 * 35 * @author Jesse Wilson 36 */ 37 public class AbstractExecutionThreadServiceTest extends TestCase { 38 39 private final TearDownStack tearDownStack = new TearDownStack(true); 40 private final CountDownLatch enterRun = new CountDownLatch(1); 41 private final CountDownLatch exitRun = new CountDownLatch(1); 42 43 private Thread executionThread; 44 private Throwable thrownByExecutionThread; 45 private final Executor exceptionCatchingExecutor = new Executor() { 46 @Override 47 public void execute(Runnable command) { 48 executionThread = new Thread(command); 49 executionThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { 50 @Override 51 public void uncaughtException(Thread thread, Throwable e) { 52 thrownByExecutionThread = e; 53 } 54 }); 55 executionThread.start(); 56 } 57 }; 58 tearDown()59 @Override protected final void tearDown() { 60 tearDownStack.runTearDown(); 61 } 62 testServiceStartStop()63 public void testServiceStartStop() throws Exception { 64 WaitOnRunService service = new WaitOnRunService(); 65 assertFalse(service.startUpCalled); 66 67 service.startAsync().awaitRunning(); 68 assertTrue(service.startUpCalled); 69 assertEquals(Service.State.RUNNING, service.state()); 70 71 enterRun.await(); // to avoid stopping the service until run() is invoked 72 73 service.stopAsync().awaitTerminated(); 74 assertTrue(service.shutDownCalled); 75 assertEquals(Service.State.TERMINATED, service.state()); 76 executionThread.join(); 77 assertNull(thrownByExecutionThread); 78 } 79 testServiceStopIdempotence()80 public void testServiceStopIdempotence() throws Exception { 81 WaitOnRunService service = new WaitOnRunService(); 82 83 service.startAsync().awaitRunning(); 84 enterRun.await(); // to avoid stopping the service until run() is invoked 85 86 service.stopAsync(); 87 service.stopAsync(); 88 service.stopAsync().awaitTerminated(); 89 assertEquals(Service.State.TERMINATED, service.state()); 90 service.stopAsync().awaitTerminated(); 91 assertEquals(Service.State.TERMINATED, service.state()); 92 93 executionThread.join(); 94 assertNull(thrownByExecutionThread); 95 } 96 testServiceExitingOnItsOwn()97 public void testServiceExitingOnItsOwn() throws Exception { 98 WaitOnRunService service = new WaitOnRunService(); 99 service.expectedShutdownState = Service.State.RUNNING; 100 101 service.startAsync().awaitRunning(); 102 assertTrue(service.startUpCalled); 103 assertEquals(Service.State.RUNNING, service.state()); 104 105 exitRun.countDown(); // the service will exit voluntarily 106 executionThread.join(); 107 108 assertTrue(service.shutDownCalled); 109 assertEquals(Service.State.TERMINATED, service.state()); 110 assertNull(thrownByExecutionThread); 111 112 service.stopAsync().awaitTerminated(); // no-op 113 assertEquals(Service.State.TERMINATED, service.state()); 114 assertTrue(service.shutDownCalled); 115 } 116 117 private class WaitOnRunService extends AbstractExecutionThreadService { 118 private boolean startUpCalled = false; 119 private boolean runCalled = false; 120 private boolean shutDownCalled = false; 121 private State expectedShutdownState = State.STOPPING; 122 startUp()123 @Override protected void startUp() { 124 assertFalse(startUpCalled); 125 assertFalse(runCalled); 126 assertFalse(shutDownCalled); 127 startUpCalled = true; 128 assertEquals(State.STARTING, state()); 129 } 130 run()131 @Override protected void run() { 132 assertTrue(startUpCalled); 133 assertFalse(runCalled); 134 assertFalse(shutDownCalled); 135 runCalled = true; 136 assertEquals(State.RUNNING, state()); 137 138 enterRun.countDown(); 139 try { 140 exitRun.await(); 141 } catch (InterruptedException e) { 142 throw new RuntimeException(e); 143 } 144 } 145 shutDown()146 @Override protected void shutDown() { 147 assertTrue(startUpCalled); 148 assertTrue(runCalled); 149 assertFalse(shutDownCalled); 150 shutDownCalled = true; 151 assertEquals(expectedShutdownState, state()); 152 } 153 triggerShutdown()154 @Override protected void triggerShutdown() { 155 exitRun.countDown(); 156 } 157 executor()158 @Override protected Executor executor() { 159 return exceptionCatchingExecutor; 160 } 161 } 162 testServiceThrowOnStartUp()163 public void testServiceThrowOnStartUp() throws Exception { 164 ThrowOnStartUpService service = new ThrowOnStartUpService(); 165 assertFalse(service.startUpCalled); 166 167 service.startAsync(); 168 try { 169 service.awaitRunning(); 170 fail(); 171 } catch (IllegalStateException expected) { 172 assertEquals("kaboom!", expected.getCause().getMessage()); 173 } 174 executionThread.join(); 175 176 assertTrue(service.startUpCalled); 177 assertEquals(Service.State.FAILED, service.state()); 178 assertTrue(thrownByExecutionThread.getMessage().equals("kaboom!")); 179 } 180 181 private class ThrowOnStartUpService extends AbstractExecutionThreadService { 182 private boolean startUpCalled = false; 183 startUp()184 @Override protected void startUp() { 185 startUpCalled = true; 186 throw new UnsupportedOperationException("kaboom!"); 187 } 188 run()189 @Override protected void run() { 190 throw new AssertionError("run() should not be called"); 191 } 192 executor()193 @Override protected Executor executor() { 194 return exceptionCatchingExecutor; 195 } 196 } 197 testServiceThrowOnRun()198 public void testServiceThrowOnRun() throws Exception { 199 ThrowOnRunService service = new ThrowOnRunService(); 200 201 service.startAsync(); 202 try { 203 service.awaitTerminated(); 204 fail(); 205 } catch (IllegalStateException expected) { 206 executionThread.join(); 207 assertEquals(thrownByExecutionThread, expected.getCause()); 208 } 209 assertTrue(service.shutDownCalled); 210 assertEquals(Service.State.FAILED, service.state()); 211 assertEquals("kaboom!", thrownByExecutionThread.getMessage()); 212 } 213 testServiceThrowOnRunAndThenAgainOnShutDown()214 public void testServiceThrowOnRunAndThenAgainOnShutDown() throws Exception { 215 ThrowOnRunService service = new ThrowOnRunService(); 216 service.throwOnShutDown = true; 217 218 service.startAsync(); 219 try { 220 service.awaitTerminated(); 221 fail(); 222 } catch (IllegalStateException expected) { 223 executionThread.join(); 224 assertEquals(thrownByExecutionThread, expected.getCause()); 225 } 226 227 assertTrue(service.shutDownCalled); 228 assertEquals(Service.State.FAILED, service.state()); 229 assertEquals("kaboom!", thrownByExecutionThread.getMessage()); 230 } 231 232 private class ThrowOnRunService extends AbstractExecutionThreadService { 233 private boolean shutDownCalled = false; 234 private boolean throwOnShutDown = false; 235 run()236 @Override protected void run() { 237 throw new UnsupportedOperationException("kaboom!"); 238 } 239 shutDown()240 @Override protected void shutDown() { 241 shutDownCalled = true; 242 if (throwOnShutDown) { 243 throw new UnsupportedOperationException("double kaboom!"); 244 } 245 } 246 executor()247 @Override protected Executor executor() { 248 return exceptionCatchingExecutor; 249 } 250 } 251 testServiceThrowOnShutDown()252 public void testServiceThrowOnShutDown() throws Exception { 253 ThrowOnShutDown service = new ThrowOnShutDown(); 254 255 service.startAsync().awaitRunning(); 256 assertEquals(Service.State.RUNNING, service.state()); 257 258 service.stopAsync(); 259 enterRun.countDown(); 260 executionThread.join(); 261 262 assertEquals(Service.State.FAILED, service.state()); 263 assertEquals("kaboom!", thrownByExecutionThread.getMessage()); 264 } 265 266 private class ThrowOnShutDown extends AbstractExecutionThreadService { run()267 @Override protected void run() { 268 try { 269 enterRun.await(); 270 } catch (InterruptedException e) { 271 throw new RuntimeException(e); 272 } 273 } 274 shutDown()275 @Override protected void shutDown() { 276 throw new UnsupportedOperationException("kaboom!"); 277 } 278 executor()279 @Override protected Executor executor() { 280 return exceptionCatchingExecutor; 281 } 282 } 283 testServiceTimeoutOnStartUp()284 public void testServiceTimeoutOnStartUp() throws Exception { 285 TimeoutOnStartUp service = new TimeoutOnStartUp(); 286 287 try { 288 service.startAsync().awaitRunning(1, TimeUnit.MILLISECONDS); 289 fail(); 290 } catch (TimeoutException e) { 291 assertTrue(e.getMessage().contains(Service.State.STARTING.toString())); 292 } 293 } 294 295 private class TimeoutOnStartUp extends AbstractExecutionThreadService { executor()296 @Override protected Executor executor() { 297 return new Executor() { 298 @Override public void execute(Runnable command) { 299 } 300 }; 301 } 302 303 @Override run()304 protected void run() throws Exception { 305 } 306 } 307 testStopWhileStarting_runNotCalled()308 public void testStopWhileStarting_runNotCalled() throws Exception { 309 final CountDownLatch started = new CountDownLatch(1); 310 FakeService service = new FakeService() { 311 @Override protected void startUp() throws Exception { 312 super.startUp(); 313 started.await(); 314 } 315 }; 316 service.startAsync(); 317 service.stopAsync(); 318 started.countDown(); 319 service.awaitTerminated(); 320 assertEquals(Service.State.TERMINATED, service.state()); 321 assertEquals(1, service.startupCalled); 322 assertEquals(0, service.runCalled); 323 assertEquals(1, service.shutdownCalled); 324 } 325 testStop_noStart()326 public void testStop_noStart() { 327 FakeService service = new FakeService(); 328 service.stopAsync().awaitTerminated(); 329 assertEquals(Service.State.TERMINATED, service.state()); 330 assertEquals(0, service.startupCalled); 331 assertEquals(0, service.runCalled); 332 assertEquals(0, service.shutdownCalled); 333 } 334 testDefaultService()335 public void testDefaultService() throws InterruptedException { 336 WaitOnRunService service = new WaitOnRunService(); 337 service.startAsync().awaitRunning(); 338 enterRun.await(); 339 service.stopAsync().awaitTerminated(); 340 } 341 342 private class FakeService extends AbstractExecutionThreadService implements TearDown { 343 344 private final ExecutorService executor = Executors.newSingleThreadExecutor(); 345 FakeService()346 FakeService() { 347 tearDownStack.addTearDown(this); 348 } 349 350 volatile int startupCalled = 0; 351 volatile int shutdownCalled = 0; 352 volatile int runCalled = 0; 353 startUp()354 @Override protected void startUp() throws Exception { 355 assertEquals(0, startupCalled); 356 assertEquals(0, runCalled); 357 assertEquals(0, shutdownCalled); 358 startupCalled++; 359 } 360 run()361 @Override protected void run() throws Exception { 362 assertEquals(1, startupCalled); 363 assertEquals(0, runCalled); 364 assertEquals(0, shutdownCalled); 365 runCalled++; 366 } 367 shutDown()368 @Override protected void shutDown() throws Exception { 369 assertEquals(1, startupCalled); 370 assertEquals(0, shutdownCalled); 371 assertEquals(Service.State.STOPPING, state()); 372 shutdownCalled++; 373 } 374 executor()375 @Override protected Executor executor() { 376 return executor; 377 } 378 tearDown()379 @Override public void tearDown() throws Exception { 380 executor.shutdown(); 381 } 382 } 383 } 384