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