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