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 com.google.common.collect.ImmutableList; 23import com.google.common.collect.Iterables; 24import com.google.common.collect.Lists; 25import com.google.common.util.concurrent.Service.Listener; 26import com.google.common.util.concurrent.Service.State; 27 28import junit.framework.TestCase; 29 30import java.lang.Thread.UncaughtExceptionHandler; 31import java.util.List; 32import java.util.concurrent.CountDownLatch; 33import java.util.concurrent.TimeUnit; 34import java.util.concurrent.atomic.AtomicInteger; 35import java.util.concurrent.atomic.AtomicReference; 36 37import javax.annotation.concurrent.GuardedBy; 38 39/** 40 * Unit test for {@link AbstractService}. 41 * 42 * @author Jesse Wilson 43 */ 44public class AbstractServiceTest extends TestCase { 45 46 private static final long LONG_TIMEOUT_MILLIS = 2500; 47 private Thread executionThread; 48 private Throwable thrownByExecutionThread; 49 50 public void testNoOpServiceStartStop() throws Exception { 51 NoOpService service = new NoOpService(); 52 RecordingListener listener = RecordingListener.record(service); 53 54 assertEquals(State.NEW, service.state()); 55 assertFalse(service.isRunning()); 56 assertFalse(service.running); 57 58 service.startAsync(); 59 assertEquals(State.RUNNING, service.state()); 60 assertTrue(service.isRunning()); 61 assertTrue(service.running); 62 63 service.stopAsync(); 64 assertEquals(State.TERMINATED, service.state()); 65 assertFalse(service.isRunning()); 66 assertFalse(service.running); 67 assertEquals( 68 ImmutableList.of( 69 State.STARTING, 70 State.RUNNING, 71 State.STOPPING, 72 State.TERMINATED), 73 listener.getStateHistory()); 74 } 75 76 public void testNoOpServiceStartAndWaitStopAndWait() throws Exception { 77 NoOpService service = new NoOpService(); 78 79 service.startAsync().awaitRunning(); 80 assertEquals(State.RUNNING, service.state()); 81 82 service.stopAsync().awaitTerminated(); 83 assertEquals(State.TERMINATED, service.state()); 84 } 85 86 public void testNoOpServiceStartAsyncAndAwaitStopAsyncAndAwait() throws Exception { 87 NoOpService service = new NoOpService(); 88 89 service.startAsync().awaitRunning(); 90 assertEquals(State.RUNNING, service.state()); 91 92 service.stopAsync().awaitTerminated(); 93 assertEquals(State.TERMINATED, service.state()); 94 } 95 96 public void testNoOpServiceStopIdempotence() throws Exception { 97 NoOpService service = new NoOpService(); 98 RecordingListener listener = RecordingListener.record(service); 99 service.startAsync().awaitRunning(); 100 assertEquals(State.RUNNING, service.state()); 101 102 service.stopAsync(); 103 service.stopAsync(); 104 assertEquals(State.TERMINATED, service.state()); 105 assertEquals( 106 ImmutableList.of( 107 State.STARTING, 108 State.RUNNING, 109 State.STOPPING, 110 State.TERMINATED), 111 listener.getStateHistory()); 112 } 113 114 public void testNoOpServiceStopIdempotenceAfterWait() throws Exception { 115 NoOpService service = new NoOpService(); 116 117 service.startAsync().awaitRunning(); 118 119 service.stopAsync().awaitTerminated(); 120 service.stopAsync(); 121 assertEquals(State.TERMINATED, service.state()); 122 } 123 124 public void testNoOpServiceStopIdempotenceDoubleWait() throws Exception { 125 NoOpService service = new NoOpService(); 126 127 service.startAsync().awaitRunning(); 128 assertEquals(State.RUNNING, service.state()); 129 130 service.stopAsync().awaitTerminated(); 131 service.stopAsync().awaitTerminated(); 132 assertEquals(State.TERMINATED, service.state()); 133 } 134 135 public void testNoOpServiceStartStopAndWaitUninterruptible() 136 throws Exception { 137 NoOpService service = new NoOpService(); 138 139 currentThread().interrupt(); 140 try { 141 service.startAsync().awaitRunning(); 142 assertEquals(State.RUNNING, service.state()); 143 144 service.stopAsync().awaitTerminated(); 145 assertEquals(State.TERMINATED, service.state()); 146 147 assertTrue(currentThread().isInterrupted()); 148 } finally { 149 Thread.interrupted(); // clear interrupt for future tests 150 } 151 } 152 153 private static class NoOpService extends AbstractService { 154 boolean running = false; 155 156 @Override protected void doStart() { 157 assertFalse(running); 158 running = true; 159 notifyStarted(); 160 } 161 162 @Override protected void doStop() { 163 assertTrue(running); 164 running = false; 165 notifyStopped(); 166 } 167 } 168 169 public void testManualServiceStartStop() throws Exception { 170 ManualSwitchedService service = new ManualSwitchedService(); 171 RecordingListener listener = RecordingListener.record(service); 172 173 service.startAsync(); 174 assertEquals(State.STARTING, service.state()); 175 assertFalse(service.isRunning()); 176 assertTrue(service.doStartCalled); 177 178 service.notifyStarted(); // usually this would be invoked by another thread 179 assertEquals(State.RUNNING, service.state()); 180 assertTrue(service.isRunning()); 181 182 service.stopAsync(); 183 assertEquals(State.STOPPING, service.state()); 184 assertFalse(service.isRunning()); 185 assertTrue(service.doStopCalled); 186 187 service.notifyStopped(); // usually this would be invoked by another thread 188 assertEquals(State.TERMINATED, service.state()); 189 assertFalse(service.isRunning()); 190 assertEquals( 191 ImmutableList.of( 192 State.STARTING, 193 State.RUNNING, 194 State.STOPPING, 195 State.TERMINATED), 196 listener.getStateHistory()); 197 198 } 199 200 public void testManualServiceNotifyStoppedWhileRunning() throws Exception { 201 ManualSwitchedService service = new ManualSwitchedService(); 202 RecordingListener listener = RecordingListener.record(service); 203 204 service.startAsync(); 205 service.notifyStarted(); 206 service.notifyStopped(); 207 assertEquals(State.TERMINATED, service.state()); 208 assertFalse(service.isRunning()); 209 assertFalse(service.doStopCalled); 210 211 assertEquals( 212 ImmutableList.of( 213 State.STARTING, 214 State.RUNNING, 215 State.TERMINATED), 216 listener.getStateHistory()); 217 } 218 219 public void testManualServiceStopWhileStarting() throws Exception { 220 ManualSwitchedService service = new ManualSwitchedService(); 221 RecordingListener listener = RecordingListener.record(service); 222 223 service.startAsync(); 224 assertEquals(State.STARTING, service.state()); 225 assertFalse(service.isRunning()); 226 assertTrue(service.doStartCalled); 227 228 service.stopAsync(); 229 assertEquals(State.STOPPING, service.state()); 230 assertFalse(service.isRunning()); 231 assertFalse(service.doStopCalled); 232 233 service.notifyStarted(); 234 assertEquals(State.STOPPING, service.state()); 235 assertFalse(service.isRunning()); 236 assertTrue(service.doStopCalled); 237 238 service.notifyStopped(); 239 assertEquals(State.TERMINATED, service.state()); 240 assertFalse(service.isRunning()); 241 assertEquals( 242 ImmutableList.of( 243 State.STARTING, 244 State.STOPPING, 245 State.TERMINATED), 246 listener.getStateHistory()); 247 } 248 249 /** 250 * This tests for a bug where if {@link Service#stopAsync()} was called while the service was 251 * {@link State#STARTING} more than once, the {@link Listener#stopping(State)} callback would get 252 * called multiple times. 253 */ 254 public void testManualServiceStopMultipleTimesWhileStarting() throws Exception { 255 ManualSwitchedService service = new ManualSwitchedService(); 256 final AtomicInteger stopppingCount = new AtomicInteger(); 257 service.addListener(new Listener() { 258 @Override public void stopping(State from) { 259 stopppingCount.incrementAndGet(); 260 } 261 }, MoreExecutors.sameThreadExecutor()); 262 263 service.startAsync(); 264 service.stopAsync(); 265 assertEquals(1, stopppingCount.get()); 266 service.stopAsync(); 267 assertEquals(1, stopppingCount.get()); 268 } 269 270 public void testManualServiceStopWhileNew() throws Exception { 271 ManualSwitchedService service = new ManualSwitchedService(); 272 RecordingListener listener = RecordingListener.record(service); 273 274 service.stopAsync(); 275 assertEquals(State.TERMINATED, service.state()); 276 assertFalse(service.isRunning()); 277 assertFalse(service.doStartCalled); 278 assertFalse(service.doStopCalled); 279 assertEquals(ImmutableList.of(State.TERMINATED), listener.getStateHistory()); 280 } 281 282 public void testManualServiceFailWhileStarting() throws Exception { 283 ManualSwitchedService service = new ManualSwitchedService(); 284 RecordingListener listener = RecordingListener.record(service); 285 service.startAsync(); 286 service.notifyFailed(EXCEPTION); 287 assertEquals(ImmutableList.of(State.STARTING, State.FAILED), listener.getStateHistory()); 288 } 289 290 public void testManualServiceFailWhileRunning() throws Exception { 291 ManualSwitchedService service = new ManualSwitchedService(); 292 RecordingListener listener = RecordingListener.record(service); 293 service.startAsync(); 294 service.notifyStarted(); 295 service.notifyFailed(EXCEPTION); 296 assertEquals(ImmutableList.of(State.STARTING, State.RUNNING, State.FAILED), 297 listener.getStateHistory()); 298 } 299 300 public void testManualServiceFailWhileStopping() throws Exception { 301 ManualSwitchedService service = new ManualSwitchedService(); 302 RecordingListener listener = RecordingListener.record(service); 303 service.startAsync(); 304 service.notifyStarted(); 305 service.stopAsync(); 306 service.notifyFailed(EXCEPTION); 307 assertEquals(ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.FAILED), 308 listener.getStateHistory()); 309 } 310 311 public void testManualServiceUnrequestedStop() { 312 ManualSwitchedService service = new ManualSwitchedService(); 313 314 service.startAsync(); 315 316 service.notifyStarted(); 317 assertEquals(State.RUNNING, service.state()); 318 assertTrue(service.isRunning()); 319 assertFalse(service.doStopCalled); 320 321 service.notifyStopped(); 322 assertEquals(State.TERMINATED, service.state()); 323 assertFalse(service.isRunning()); 324 assertFalse(service.doStopCalled); 325 } 326 327 /** 328 * The user of this service should call {@link #notifyStarted} and {@link 329 * #notifyStopped} after calling {@link #startAsync} and {@link #stopAsync}. 330 */ 331 private static class ManualSwitchedService extends AbstractService { 332 boolean doStartCalled = false; 333 boolean doStopCalled = false; 334 335 @Override protected void doStart() { 336 assertFalse(doStartCalled); 337 doStartCalled = true; 338 } 339 340 @Override protected void doStop() { 341 assertFalse(doStopCalled); 342 doStopCalled = true; 343 } 344 } 345 346 public void testAwaitTerminated() throws Exception { 347 final NoOpService service = new NoOpService(); 348 Thread waiter = new Thread() { 349 @Override public void run() { 350 service.awaitTerminated(); 351 } 352 }; 353 waiter.start(); 354 service.startAsync().awaitRunning(); 355 assertEquals(State.RUNNING, service.state()); 356 service.stopAsync(); 357 waiter.join(LONG_TIMEOUT_MILLIS); // ensure that the await in the other thread is triggered 358 assertFalse(waiter.isAlive()); 359 } 360 361 public void testAwaitTerminated_FailedService() throws Exception { 362 final ManualSwitchedService service = new ManualSwitchedService(); 363 final AtomicReference<Throwable> exception = Atomics.newReference(); 364 Thread waiter = new Thread() { 365 @Override public void run() { 366 try { 367 service.awaitTerminated(); 368 fail("Expected an IllegalStateException"); 369 } catch (Throwable t) { 370 exception.set(t); 371 } 372 } 373 }; 374 waiter.start(); 375 service.startAsync(); 376 service.notifyStarted(); 377 assertEquals(State.RUNNING, service.state()); 378 service.notifyFailed(EXCEPTION); 379 assertEquals(State.FAILED, service.state()); 380 waiter.join(LONG_TIMEOUT_MILLIS); 381 assertFalse(waiter.isAlive()); 382 assertTrue(exception.get() instanceof IllegalStateException); 383 assertEquals(EXCEPTION, exception.get().getCause()); 384 } 385 386 public void testThreadedServiceStartAndWaitStopAndWait() throws Throwable { 387 ThreadedService service = new ThreadedService(); 388 RecordingListener listener = RecordingListener.record(service); 389 service.startAsync().awaitRunning(); 390 assertEquals(State.RUNNING, service.state()); 391 392 service.awaitRunChecks(); 393 394 service.stopAsync().awaitTerminated(); 395 assertEquals(State.TERMINATED, service.state()); 396 397 throwIfSet(thrownByExecutionThread); 398 assertEquals( 399 ImmutableList.of( 400 State.STARTING, 401 State.RUNNING, 402 State.STOPPING, 403 State.TERMINATED), 404 listener.getStateHistory()); 405 } 406 407 public void testThreadedServiceStopIdempotence() throws Throwable { 408 ThreadedService service = new ThreadedService(); 409 410 service.startAsync().awaitRunning(); 411 assertEquals(State.RUNNING, service.state()); 412 413 service.awaitRunChecks(); 414 415 service.stopAsync(); 416 service.stopAsync().awaitTerminated(); 417 assertEquals(State.TERMINATED, service.state()); 418 419 throwIfSet(thrownByExecutionThread); 420 } 421 422 public void testThreadedServiceStopIdempotenceAfterWait() 423 throws Throwable { 424 ThreadedService service = new ThreadedService(); 425 426 service.startAsync().awaitRunning(); 427 assertEquals(State.RUNNING, service.state()); 428 429 service.awaitRunChecks(); 430 431 service.stopAsync().awaitTerminated(); 432 service.stopAsync(); 433 assertEquals(State.TERMINATED, service.state()); 434 435 executionThread.join(); 436 437 throwIfSet(thrownByExecutionThread); 438 } 439 440 public void testThreadedServiceStopIdempotenceDoubleWait() 441 throws Throwable { 442 ThreadedService service = new ThreadedService(); 443 444 service.startAsync().awaitRunning(); 445 assertEquals(State.RUNNING, service.state()); 446 447 service.awaitRunChecks(); 448 449 service.stopAsync().awaitTerminated(); 450 service.stopAsync().awaitTerminated(); 451 assertEquals(State.TERMINATED, service.state()); 452 453 throwIfSet(thrownByExecutionThread); 454 } 455 456 public void testManualServiceFailureIdempotence() { 457 ManualSwitchedService service = new ManualSwitchedService(); 458 RecordingListener.record(service); 459 service.startAsync(); 460 service.notifyFailed(new Exception("1")); 461 service.notifyFailed(new Exception("2")); 462 assertEquals("1", service.failureCause().getMessage()); 463 try { 464 service.awaitRunning(); 465 fail(); 466 } catch (IllegalStateException e) { 467 assertEquals("1", e.getCause().getMessage()); 468 } 469 } 470 471 private class ThreadedService extends AbstractService { 472 final CountDownLatch hasConfirmedIsRunning = new CountDownLatch(1); 473 474 /* 475 * The main test thread tries to stop() the service shortly after 476 * confirming that it is running. Meanwhile, the service itself is trying 477 * to confirm that it is running. If the main thread's stop() call happens 478 * before it has the chance, the test will fail. To avoid this, the main 479 * thread calls this method, which waits until the service has performed 480 * its own "running" check. 481 */ 482 void awaitRunChecks() throws InterruptedException { 483 assertTrue("Service thread hasn't finished its checks. " 484 + "Exception status (possibly stale): " + thrownByExecutionThread, 485 hasConfirmedIsRunning.await(10, SECONDS)); 486 } 487 488 @Override protected void doStart() { 489 assertEquals(State.STARTING, state()); 490 invokeOnExecutionThreadForTest(new Runnable() { 491 @Override public void run() { 492 assertEquals(State.STARTING, state()); 493 notifyStarted(); 494 assertEquals(State.RUNNING, state()); 495 hasConfirmedIsRunning.countDown(); 496 } 497 }); 498 } 499 500 @Override protected void doStop() { 501 assertEquals(State.STOPPING, state()); 502 invokeOnExecutionThreadForTest(new Runnable() { 503 @Override public void run() { 504 assertEquals(State.STOPPING, state()); 505 notifyStopped(); 506 assertEquals(State.TERMINATED, state()); 507 } 508 }); 509 } 510 } 511 512 private void invokeOnExecutionThreadForTest(Runnable runnable) { 513 executionThread = new Thread(runnable); 514 executionThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { 515 @Override 516 public void uncaughtException(Thread thread, Throwable e) { 517 thrownByExecutionThread = e; 518 } 519 }); 520 executionThread.start(); 521 } 522 523 private static void throwIfSet(Throwable t) throws Throwable { 524 if (t != null) { 525 throw t; 526 } 527 } 528 529 public void testStopUnstartedService() throws Exception { 530 NoOpService service = new NoOpService(); 531 RecordingListener listener = RecordingListener.record(service); 532 533 service.stopAsync(); 534 assertEquals(State.TERMINATED, service.state()); 535 536 try { 537 service.startAsync(); 538 fail(); 539 } catch (IllegalStateException expected) {} 540 assertEquals(State.TERMINATED, Iterables.getOnlyElement(listener.getStateHistory())); 541 } 542 543 public void testFailingServiceStartAndWait() throws Exception { 544 StartFailingService service = new StartFailingService(); 545 RecordingListener listener = RecordingListener.record(service); 546 547 try { 548 service.startAsync().awaitRunning(); 549 fail(); 550 } catch (IllegalStateException e) { 551 assertEquals(EXCEPTION, service.failureCause()); 552 assertEquals(EXCEPTION, e.getCause()); 553 } 554 assertEquals( 555 ImmutableList.of( 556 State.STARTING, 557 State.FAILED), 558 listener.getStateHistory()); 559 } 560 561 public void testFailingServiceStopAndWait_stopFailing() throws Exception { 562 StopFailingService service = new StopFailingService(); 563 RecordingListener listener = RecordingListener.record(service); 564 565 service.startAsync().awaitRunning(); 566 try { 567 service.stopAsync().awaitTerminated(); 568 fail(); 569 } catch (IllegalStateException e) { 570 assertEquals(EXCEPTION, service.failureCause()); 571 assertEquals(EXCEPTION, e.getCause()); 572 } 573 assertEquals( 574 ImmutableList.of( 575 State.STARTING, 576 State.RUNNING, 577 State.STOPPING, 578 State.FAILED), 579 listener.getStateHistory()); 580 } 581 582 public void testFailingServiceStopAndWait_runFailing() throws Exception { 583 RunFailingService service = new RunFailingService(); 584 RecordingListener listener = RecordingListener.record(service); 585 586 service.startAsync(); 587 try { 588 service.awaitRunning(); 589 fail(); 590 } catch (IllegalStateException e) { 591 assertEquals(EXCEPTION, service.failureCause()); 592 assertEquals(EXCEPTION, e.getCause()); 593 } 594 assertEquals( 595 ImmutableList.of( 596 State.STARTING, 597 State.RUNNING, 598 State.FAILED), 599 listener.getStateHistory()); 600 } 601 602 public void testThrowingServiceStartAndWait() throws Exception { 603 StartThrowingService service = new StartThrowingService(); 604 RecordingListener listener = RecordingListener.record(service); 605 606 try { 607 service.startAsync().awaitRunning(); 608 fail(); 609 } catch (IllegalStateException e) { 610 assertEquals(service.exception, service.failureCause()); 611 assertEquals(service.exception, e.getCause()); 612 } 613 assertEquals( 614 ImmutableList.of( 615 State.STARTING, 616 State.FAILED), 617 listener.getStateHistory()); 618 } 619 620 public void testThrowingServiceStopAndWait_stopThrowing() throws Exception { 621 StopThrowingService service = new StopThrowingService(); 622 RecordingListener listener = RecordingListener.record(service); 623 624 service.startAsync().awaitRunning(); 625 try { 626 service.stopAsync().awaitTerminated(); 627 fail(); 628 } catch (IllegalStateException e) { 629 assertEquals(service.exception, service.failureCause()); 630 assertEquals(service.exception, e.getCause()); 631 } 632 assertEquals( 633 ImmutableList.of( 634 State.STARTING, 635 State.RUNNING, 636 State.STOPPING, 637 State.FAILED), 638 listener.getStateHistory()); 639 } 640 641 public void testThrowingServiceStopAndWait_runThrowing() throws Exception { 642 RunThrowingService service = new RunThrowingService(); 643 RecordingListener listener = RecordingListener.record(service); 644 645 service.startAsync(); 646 try { 647 service.awaitTerminated(); 648 fail(); 649 } catch (IllegalStateException e) { 650 assertEquals(service.exception, service.failureCause()); 651 assertEquals(service.exception, e.getCause()); 652 } 653 assertEquals( 654 ImmutableList.of( 655 State.STARTING, 656 State.RUNNING, 657 State.FAILED), 658 listener.getStateHistory()); 659 } 660 661 public void testFailureCause_throwsIfNotFailed() { 662 StopFailingService service = new StopFailingService(); 663 try { 664 service.failureCause(); 665 fail(); 666 } catch (IllegalStateException e) { 667 // expected 668 } 669 service.startAsync().awaitRunning(); 670 try { 671 service.failureCause(); 672 fail(); 673 } catch (IllegalStateException e) { 674 // expected 675 } 676 try { 677 service.stopAsync().awaitTerminated(); 678 fail(); 679 } catch (IllegalStateException e) { 680 assertEquals(EXCEPTION, service.failureCause()); 681 assertEquals(EXCEPTION, e.getCause()); 682 } 683 } 684 685 public void testAddListenerAfterFailureDoesntCauseDeadlock() throws InterruptedException { 686 final StartFailingService service = new StartFailingService(); 687 service.startAsync(); 688 assertEquals(State.FAILED, service.state()); 689 service.addListener(new RecordingListener(service), MoreExecutors.sameThreadExecutor()); 690 Thread thread = new Thread() { 691 @Override public void run() { 692 // Internally stopAsync() grabs a lock, this could be any such method on AbstractService. 693 service.stopAsync(); 694 } 695 }; 696 thread.start(); 697 thread.join(LONG_TIMEOUT_MILLIS); 698 assertFalse(thread + " is deadlocked", thread.isAlive()); 699 } 700 701 public void testListenerDoesntDeadlockOnStartAndWaitFromRunning() throws Exception { 702 final NoOpThreadedService service = new NoOpThreadedService(); 703 service.addListener(new Listener() { 704 @Override public void running() { 705 service.awaitRunning(); 706 } 707 }, MoreExecutors.sameThreadExecutor()); 708 service.startAsync().awaitRunning(LONG_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 709 service.stopAsync(); 710 } 711 712 public void testListenerDoesntDeadlockOnStopAndWaitFromTerminated() throws Exception { 713 final NoOpThreadedService service = new NoOpThreadedService(); 714 service.addListener(new Listener() { 715 @Override public void terminated(State from) { 716 service.stopAsync().awaitTerminated(); 717 } 718 }, MoreExecutors.sameThreadExecutor()); 719 service.startAsync().awaitRunning(); 720 721 Thread thread = new Thread() { 722 @Override public void run() { 723 service.stopAsync().awaitTerminated(); 724 } 725 }; 726 thread.start(); 727 thread.join(LONG_TIMEOUT_MILLIS); 728 assertFalse(thread + " is deadlocked", thread.isAlive()); 729 } 730 731 private static class NoOpThreadedService extends AbstractExecutionThreadService { 732 final CountDownLatch latch = new CountDownLatch(1); 733 @Override protected void run() throws Exception { 734 latch.await(); 735 } 736 @Override protected void triggerShutdown() { 737 latch.countDown(); 738 } 739 } 740 741 private static class StartFailingService extends AbstractService { 742 @Override protected void doStart() { 743 notifyFailed(EXCEPTION); 744 } 745 746 @Override protected void doStop() { 747 fail(); 748 } 749 } 750 751 private static class RunFailingService extends AbstractService { 752 @Override protected void doStart() { 753 notifyStarted(); 754 notifyFailed(EXCEPTION); 755 } 756 757 @Override protected void doStop() { 758 fail(); 759 } 760 } 761 762 private static class StopFailingService extends AbstractService { 763 @Override protected void doStart() { 764 notifyStarted(); 765 } 766 767 @Override protected void doStop() { 768 notifyFailed(EXCEPTION); 769 } 770 } 771 772 private static class StartThrowingService extends AbstractService { 773 774 final RuntimeException exception = new RuntimeException("deliberate"); 775 776 @Override protected void doStart() { 777 throw exception; 778 } 779 780 @Override protected void doStop() { 781 fail(); 782 } 783 } 784 785 private static class RunThrowingService extends AbstractService { 786 787 final RuntimeException exception = new RuntimeException("deliberate"); 788 789 @Override protected void doStart() { 790 notifyStarted(); 791 throw exception; 792 } 793 794 @Override protected void doStop() { 795 fail(); 796 } 797 } 798 799 private static class StopThrowingService extends AbstractService { 800 801 final RuntimeException exception = new RuntimeException("deliberate"); 802 803 @Override protected void doStart() { 804 notifyStarted(); 805 } 806 807 @Override protected void doStop() { 808 throw exception; 809 } 810 } 811 812 private static class RecordingListener extends Listener { 813 static RecordingListener record(Service service) { 814 RecordingListener listener = new RecordingListener(service); 815 service.addListener(listener, MoreExecutors.sameThreadExecutor()); 816 return listener; 817 } 818 819 final Service service; 820 821 RecordingListener(Service service) { 822 this.service = service; 823 } 824 825 @GuardedBy("this") 826 final List<State> stateHistory = Lists.newArrayList(); 827 final CountDownLatch completionLatch = new CountDownLatch(1); 828 829 ImmutableList<State> getStateHistory() throws Exception { 830 completionLatch.await(); 831 synchronized (this) { 832 return ImmutableList.copyOf(stateHistory); 833 } 834 } 835 836 @Override public synchronized void starting() { 837 assertTrue(stateHistory.isEmpty()); 838 assertNotSame(State.NEW, service.state()); 839 stateHistory.add(State.STARTING); 840 } 841 842 @Override public synchronized void running() { 843 assertEquals(State.STARTING, Iterables.getOnlyElement(stateHistory)); 844 stateHistory.add(State.RUNNING); 845 service.awaitRunning(); 846 assertNotSame(State.STARTING, service.state()); 847 } 848 849 @Override public synchronized void stopping(State from) { 850 assertEquals(from, Iterables.getLast(stateHistory)); 851 stateHistory.add(State.STOPPING); 852 if (from == State.STARTING) { 853 try { 854 service.awaitRunning(); 855 fail(); 856 } catch (IllegalStateException expected) { 857 assertNull(expected.getCause()); 858 assertTrue(expected.getMessage().equals( 859 "Expected the service to be RUNNING, but was STOPPING")); 860 } 861 } 862 assertNotSame(from, service.state()); 863 } 864 865 @Override public synchronized void terminated(State from) { 866 assertEquals(from, Iterables.getLast(stateHistory, State.NEW)); 867 stateHistory.add(State.TERMINATED); 868 assertEquals(State.TERMINATED, service.state()); 869 if (from == State.NEW) { 870 try { 871 service.awaitRunning(); 872 fail(); 873 } catch (IllegalStateException expected) { 874 assertNull(expected.getCause()); 875 assertTrue(expected.getMessage().equals( 876 "Expected the service to be RUNNING, but was TERMINATED")); 877 } 878 } 879 completionLatch.countDown(); 880 } 881 882 @Override public synchronized void failed(State from, Throwable failure) { 883 assertEquals(from, Iterables.getLast(stateHistory)); 884 stateHistory.add(State.FAILED); 885 assertEquals(State.FAILED, service.state()); 886 assertEquals(failure, service.failureCause()); 887 if (from == State.STARTING) { 888 try { 889 service.awaitRunning(); 890 fail(); 891 } catch (IllegalStateException e) { 892 assertEquals(failure, e.getCause()); 893 } 894 } 895 try { 896 service.awaitTerminated(); 897 fail(); 898 } catch (IllegalStateException e) { 899 assertEquals(failure, e.getCause()); 900 } 901 completionLatch.countDown(); 902 } 903 } 904 905 public void testNotifyStartedWhenNotStarting() { 906 AbstractService service = new DefaultService(); 907 try { 908 service.notifyStarted(); 909 fail(); 910 } catch (IllegalStateException expected) {} 911 } 912 913 public void testNotifyStoppedWhenNotRunning() { 914 AbstractService service = new DefaultService(); 915 try { 916 service.notifyStopped(); 917 fail(); 918 } catch (IllegalStateException expected) {} 919 } 920 921 public void testNotifyFailedWhenNotStarted() { 922 AbstractService service = new DefaultService(); 923 try { 924 service.notifyFailed(new Exception()); 925 fail(); 926 } catch (IllegalStateException expected) {} 927 } 928 929 public void testNotifyFailedWhenTerminated() { 930 NoOpService service = new NoOpService(); 931 service.startAsync().awaitRunning(); 932 service.stopAsync().awaitTerminated(); 933 try { 934 service.notifyFailed(new Exception()); 935 fail(); 936 } catch (IllegalStateException expected) {} 937 } 938 939 private static class DefaultService extends AbstractService { 940 @Override protected void doStart() {} 941 @Override protected void doStop() {} 942 } 943 944 private static final Exception EXCEPTION = new Exception(); 945} 946