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 static java.lang.Thread.currentThread; 20import static java.util.concurrent.TimeUnit.SECONDS; 21 22import junit.framework.Assert; 23import junit.framework.TestCase; 24 25import java.lang.Thread.UncaughtExceptionHandler; 26import java.util.concurrent.CountDownLatch; 27import java.util.concurrent.Future; 28 29/** 30 * Unit test for {@link AbstractService}. 31 * 32 * @author Jesse Wilson 33 */ 34public class AbstractServiceTest extends TestCase { 35 36 private Thread executionThread; 37 private Throwable thrownByExecutionThread; 38 39 public void testNoOpServiceStartStop() { 40 NoOpService service = new NoOpService(); 41 Assert.assertEquals(Service.State.NEW, service.state()); 42 assertFalse(service.isRunning()); 43 assertFalse(service.running); 44 45 service.start(); 46 assertEquals(Service.State.RUNNING, service.state()); 47 assertTrue(service.isRunning()); 48 assertTrue(service.running); 49 50 service.stop(); 51 assertEquals(Service.State.TERMINATED, service.state()); 52 assertFalse(service.isRunning()); 53 assertFalse(service.running); 54 } 55 56 public void testNoOpServiceStartAndWaitStopAndWait() throws Exception { 57 NoOpService service = new NoOpService(); 58 59 service.start().get(); 60 assertEquals(Service.State.RUNNING, service.state()); 61 62 service.stop().get(); 63 assertEquals(Service.State.TERMINATED, service.state()); 64 } 65 66 public void testNoOpServiceStartStopIdempotence() throws Exception { 67 NoOpService service = new NoOpService(); 68 69 service.start(); 70 service.start(); 71 assertEquals(Service.State.RUNNING, service.state()); 72 73 service.stop(); 74 service.stop(); 75 assertEquals(Service.State.TERMINATED, service.state()); 76 } 77 78 public void testNoOpServiceStartStopIdempotenceAfterWait() throws Exception { 79 NoOpService service = new NoOpService(); 80 81 service.start().get(); 82 service.start(); 83 assertEquals(Service.State.RUNNING, service.state()); 84 85 service.stop().get(); 86 service.stop(); 87 assertEquals(Service.State.TERMINATED, service.state()); 88 } 89 90 public void testNoOpServiceStartStopIdempotenceDoubleWait() throws Exception { 91 NoOpService service = new NoOpService(); 92 93 service.start().get(); 94 service.start().get(); 95 assertEquals(Service.State.RUNNING, service.state()); 96 97 service.stop().get(); 98 service.stop().get(); 99 assertEquals(Service.State.TERMINATED, service.state()); 100 } 101 102 public void testNoOpServiceStartStopAndWaitUninterruptible() 103 throws Exception { 104 NoOpService service = new NoOpService(); 105 106 currentThread().interrupt(); 107 try { 108 service.startAndWait(); 109 assertEquals(Service.State.RUNNING, service.state()); 110 111 service.stopAndWait(); 112 assertEquals(Service.State.TERMINATED, service.state()); 113 114 assertTrue(currentThread().isInterrupted()); 115 } finally { 116 Thread.interrupted(); // clear interrupt for future tests 117 } 118 } 119 120 private static class NoOpService extends AbstractService { 121 boolean running = false; 122 123 @Override protected void doStart() { 124 assertFalse(running); 125 running = true; 126 notifyStarted(); 127 } 128 129 @Override protected void doStop() { 130 assertTrue(running); 131 running = false; 132 notifyStopped(); 133 } 134 } 135 136 public void testManualServiceStartStop() { 137 ManualSwitchedService service = new ManualSwitchedService(); 138 139 service.start(); 140 assertEquals(Service.State.STARTING, service.state()); 141 assertFalse(service.isRunning()); 142 assertTrue(service.doStartCalled); 143 144 service.notifyStarted(); // usually this would be invoked by another thread 145 assertEquals(Service.State.RUNNING, service.state()); 146 assertTrue(service.isRunning()); 147 148 service.stop(); 149 assertEquals(Service.State.STOPPING, service.state()); 150 assertFalse(service.isRunning()); 151 assertTrue(service.doStopCalled); 152 153 service.notifyStopped(); // usually this would be invoked by another thread 154 assertEquals(Service.State.TERMINATED, service.state()); 155 assertFalse(service.isRunning()); 156 } 157 158 public void testManualServiceStopWhileStarting() { 159 ManualSwitchedService service = new ManualSwitchedService(); 160 161 service.start(); 162 assertEquals(Service.State.STARTING, service.state()); 163 assertFalse(service.isRunning()); 164 assertTrue(service.doStartCalled); 165 166 service.stop(); 167 assertEquals(Service.State.STOPPING, service.state()); 168 assertFalse(service.isRunning()); 169 assertFalse(service.doStopCalled); 170 171 service.notifyStarted(); 172 assertEquals(Service.State.STOPPING, service.state()); 173 assertFalse(service.isRunning()); 174 assertTrue(service.doStopCalled); 175 176 service.notifyStopped(); 177 assertEquals(Service.State.TERMINATED, service.state()); 178 assertFalse(service.isRunning()); 179 } 180 181 public void testManualServiceUnrequestedStop() { 182 ManualSwitchedService service = new ManualSwitchedService(); 183 184 service.start(); 185 186 service.notifyStarted(); 187 assertEquals(Service.State.RUNNING, service.state()); 188 assertTrue(service.isRunning()); 189 assertFalse(service.doStopCalled); 190 191 service.notifyStopped(); 192 assertEquals(Service.State.TERMINATED, service.state()); 193 assertFalse(service.isRunning()); 194 assertFalse(service.doStopCalled); 195 } 196 197 /** 198 * The user of this service should call {@link #notifyStarted} and {@link 199 * #notifyStopped} after calling {@link #start} and {@link #stop}. 200 */ 201 private static class ManualSwitchedService extends AbstractService { 202 boolean doStartCalled = false; 203 boolean doStopCalled = false; 204 205 @Override protected void doStart() { 206 assertFalse(doStartCalled); 207 doStartCalled = true; 208 } 209 210 @Override protected void doStop() { 211 assertFalse(doStopCalled); 212 doStopCalled = true; 213 } 214 } 215 216 public void testThreadedServiceStartAndWaitStopAndWait() throws Throwable { 217 ThreadedService service = new ThreadedService(); 218 219 service.start().get(); 220 assertEquals(Service.State.RUNNING, service.state()); 221 222 service.awaitRunChecks(); 223 224 service.stop().get(); 225 assertEquals(Service.State.TERMINATED, service.state()); 226 227 throwIfSet(thrownByExecutionThread); 228 } 229 230 public void testThreadedServiceStartStopIdempotence() throws Throwable { 231 ThreadedService service = new ThreadedService(); 232 233 service.start(); 234 service.start().get(); 235 assertEquals(Service.State.RUNNING, service.state()); 236 237 service.awaitRunChecks(); 238 239 service.stop(); 240 service.stop().get(); 241 assertEquals(Service.State.TERMINATED, service.state()); 242 243 throwIfSet(thrownByExecutionThread); 244 } 245 246 public void testThreadedServiceStartStopIdempotenceAfterWait() 247 throws Throwable { 248 ThreadedService service = new ThreadedService(); 249 250 service.start().get(); 251 service.start(); 252 assertEquals(Service.State.RUNNING, service.state()); 253 254 service.awaitRunChecks(); 255 256 service.stop().get(); 257 service.stop(); 258 assertEquals(Service.State.TERMINATED, service.state()); 259 260 executionThread.join(); 261 262 throwIfSet(thrownByExecutionThread); 263 } 264 265 public void testThreadedServiceStartStopIdempotenceDoubleWait() 266 throws Throwable { 267 ThreadedService service = new ThreadedService(); 268 269 service.start().get(); 270 service.start().get(); 271 assertEquals(Service.State.RUNNING, service.state()); 272 273 service.awaitRunChecks(); 274 275 service.stop().get(); 276 service.stop().get(); 277 assertEquals(Service.State.TERMINATED, service.state()); 278 279 throwIfSet(thrownByExecutionThread); 280 } 281 282 private class ThreadedService extends AbstractService { 283 final CountDownLatch hasConfirmedIsRunning = new CountDownLatch(1); 284 285 /* 286 * The main test thread tries to stop() the service shortly after 287 * confirming that it is running. Meanwhile, the service itself is trying 288 * to confirm that it is running. If the main thread's stop() call happens 289 * before it has the chance, the test will fail. To avoid this, the main 290 * thread calls this method, which waits until the service has performed 291 * its own "running" check. 292 */ 293 void awaitRunChecks() throws InterruptedException { 294 assertTrue("Service thread hasn't finished its checks. " 295 + "Exception status (possibly stale): " + thrownByExecutionThread, 296 hasConfirmedIsRunning.await(10, SECONDS)); 297 } 298 299 @Override protected void doStart() { 300 assertEquals(State.STARTING, state()); 301 invokeOnExecutionThreadForTest(new Runnable() { 302 @Override public void run() { 303 assertEquals(State.STARTING, state()); 304 notifyStarted(); 305 assertEquals(State.RUNNING, state()); 306 hasConfirmedIsRunning.countDown(); 307 } 308 }); 309 } 310 311 @Override protected void doStop() { 312 assertEquals(State.STOPPING, state()); 313 invokeOnExecutionThreadForTest(new Runnable() { 314 @Override public void run() { 315 assertEquals(State.STOPPING, state()); 316 notifyStopped(); 317 assertEquals(State.TERMINATED, state()); 318 } 319 }); 320 } 321 } 322 323 private void invokeOnExecutionThreadForTest(Runnable runnable) { 324 executionThread = new Thread(runnable); 325 executionThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { 326 @Override 327 public void uncaughtException(Thread thread, Throwable e) { 328 thrownByExecutionThread = e; 329 } 330 }); 331 executionThread.start(); 332 } 333 334 private static void throwIfSet(Throwable t) throws Throwable { 335 if (t != null) { 336 throw t; 337 } 338 } 339 340 public void testStopUnstartedService() throws Exception { 341 NoOpService service = new NoOpService(); 342 Future<Service.State> stopResult = service.stop(); 343 assertEquals(Service.State.TERMINATED, service.state()); 344 assertEquals(Service.State.TERMINATED, stopResult.get()); 345 346 Future<Service.State> startResult = service.start(); 347 assertEquals(Service.State.TERMINATED, service.state()); 348 assertEquals(Service.State.TERMINATED, startResult.get()); 349 } 350 351 public void testThrowingServiceStartAndWait() throws Exception { 352 StartThrowingService service = new StartThrowingService(); 353 354 try { 355 service.startAndWait(); 356 fail(); 357 } catch (UncheckedExecutionException e) { 358 assertEquals(EXCEPTION, e.getCause()); 359 } 360 } 361 362 public void testThrowingServiceStopAndWait_stopThrowing() throws Exception { 363 StopThrowingService service = new StopThrowingService(); 364 365 service.startAndWait(); 366 try { 367 service.stopAndWait(); 368 fail(); 369 } catch (UncheckedExecutionException e) { 370 assertEquals(EXCEPTION, e.getCause()); 371 } 372 } 373 374 public void testThrowingServiceStopAndWait_runThrowing() throws Exception { 375 RunThrowingService service = new RunThrowingService(); 376 377 service.startAndWait(); 378 try { 379 service.stopAndWait(); 380 fail(); 381 } catch (UncheckedExecutionException e) { 382 assertEquals(EXCEPTION, e.getCause().getCause()); 383 } 384 } 385 386 private static class StartThrowingService extends AbstractService { 387 @Override protected void doStart() { 388 notifyFailed(EXCEPTION); 389 } 390 391 @Override protected void doStop() { 392 fail(); 393 } 394 } 395 396 private static class RunThrowingService extends AbstractService { 397 @Override protected void doStart() { 398 notifyStarted(); 399 notifyFailed(EXCEPTION); 400 } 401 402 @Override protected void doStop() { 403 fail(); 404 } 405 } 406 407 private static class StopThrowingService extends AbstractService { 408 @Override protected void doStart() { 409 notifyStarted(); 410 } 411 412 @Override protected void doStop() { 413 notifyFailed(EXCEPTION); 414 } 415 } 416 417 private static final Exception EXCEPTION = new Exception(); 418} 419