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