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 com.google.common.testing.TearDown;
20import com.google.common.testing.TearDownStack;
21
22import junit.framework.TestCase;
23
24import java.lang.Thread.UncaughtExceptionHandler;
25import java.util.concurrent.CountDownLatch;
26import java.util.concurrent.Executor;
27import java.util.concurrent.ExecutorService;
28import java.util.concurrent.Executors;
29import java.util.concurrent.TimeUnit;
30import java.util.concurrent.TimeoutException;
31
32/**
33 * Unit test for {@link AbstractExecutionThreadService}.
34 *
35 * @author Jesse Wilson
36 */
37public class AbstractExecutionThreadServiceTest extends TestCase {
38
39  private final TearDownStack tearDownStack = new TearDownStack(true);
40  private final CountDownLatch enterRun = new CountDownLatch(1);
41  private final CountDownLatch exitRun = new CountDownLatch(1);
42
43  private Thread executionThread;
44  private Throwable thrownByExecutionThread;
45  private final Executor exceptionCatchingExecutor = new Executor() {
46    @Override
47    public void execute(Runnable command) {
48      executionThread = new Thread(command);
49      executionThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
50        @Override
51        public void uncaughtException(Thread thread, Throwable e) {
52          thrownByExecutionThread = e;
53        }
54      });
55      executionThread.start();
56    }
57  };
58
59  @Override protected final void tearDown() {
60    tearDownStack.runTearDown();
61  }
62
63  public void testServiceStartStop() throws Exception {
64    WaitOnRunService service = new WaitOnRunService();
65    assertFalse(service.startUpCalled);
66
67    service.startAsync().awaitRunning();
68    assertTrue(service.startUpCalled);
69    assertEquals(Service.State.RUNNING, service.state());
70
71    enterRun.await(); // to avoid stopping the service until run() is invoked
72
73    service.stopAsync().awaitTerminated();
74    assertTrue(service.shutDownCalled);
75    assertEquals(Service.State.TERMINATED, service.state());
76    executionThread.join();
77    assertNull(thrownByExecutionThread);
78  }
79
80  public void testServiceStopIdempotence() throws Exception {
81    WaitOnRunService service = new WaitOnRunService();
82
83    service.startAsync().awaitRunning();
84    enterRun.await(); // to avoid stopping the service until run() is invoked
85
86    service.stopAsync();
87    service.stopAsync();
88    service.stopAsync().awaitTerminated();
89    assertEquals(Service.State.TERMINATED, service.state());
90    service.stopAsync().awaitTerminated();
91    assertEquals(Service.State.TERMINATED, service.state());
92
93    executionThread.join();
94    assertNull(thrownByExecutionThread);
95  }
96
97  public void testServiceExitingOnItsOwn() throws Exception {
98    WaitOnRunService service = new WaitOnRunService();
99    service.expectedShutdownState = Service.State.RUNNING;
100
101    service.startAsync().awaitRunning();
102    assertTrue(service.startUpCalled);
103    assertEquals(Service.State.RUNNING, service.state());
104
105    exitRun.countDown(); // the service will exit voluntarily
106    executionThread.join();
107
108    assertTrue(service.shutDownCalled);
109    assertEquals(Service.State.TERMINATED, service.state());
110    assertNull(thrownByExecutionThread);
111
112    service.stopAsync().awaitTerminated(); // no-op
113    assertEquals(Service.State.TERMINATED, service.state());
114    assertTrue(service.shutDownCalled);
115  }
116
117  private class WaitOnRunService extends AbstractExecutionThreadService {
118    private boolean startUpCalled = false;
119    private boolean runCalled = false;
120    private boolean shutDownCalled = false;
121    private State expectedShutdownState = State.STOPPING;
122
123    @Override protected void startUp() {
124      assertFalse(startUpCalled);
125      assertFalse(runCalled);
126      assertFalse(shutDownCalled);
127      startUpCalled = true;
128      assertEquals(State.STARTING, state());
129    }
130
131    @Override protected void run() {
132      assertTrue(startUpCalled);
133      assertFalse(runCalled);
134      assertFalse(shutDownCalled);
135      runCalled = true;
136      assertEquals(State.RUNNING, state());
137
138      enterRun.countDown();
139      try {
140        exitRun.await();
141      } catch (InterruptedException e) {
142        throw new RuntimeException(e);
143      }
144    }
145
146    @Override protected void shutDown() {
147      assertTrue(startUpCalled);
148      assertTrue(runCalled);
149      assertFalse(shutDownCalled);
150      shutDownCalled = true;
151      assertEquals(expectedShutdownState, state());
152    }
153
154    @Override protected void triggerShutdown() {
155      exitRun.countDown();
156    }
157
158    @Override protected Executor executor() {
159      return exceptionCatchingExecutor;
160    }
161  }
162
163  public void testServiceThrowOnStartUp() throws Exception {
164    ThrowOnStartUpService service = new ThrowOnStartUpService();
165    assertFalse(service.startUpCalled);
166
167    service.startAsync();
168    try {
169      service.awaitRunning();
170      fail();
171    } catch (IllegalStateException expected) {
172      assertEquals("kaboom!", expected.getCause().getMessage());
173    }
174    executionThread.join();
175
176    assertTrue(service.startUpCalled);
177    assertEquals(Service.State.FAILED, service.state());
178    assertTrue(thrownByExecutionThread.getMessage().equals("kaboom!"));
179  }
180
181  private class ThrowOnStartUpService extends AbstractExecutionThreadService {
182    private boolean startUpCalled = false;
183
184    @Override protected void startUp() {
185      startUpCalled = true;
186      throw new UnsupportedOperationException("kaboom!");
187    }
188
189    @Override protected void run() {
190      throw new AssertionError("run() should not be called");
191    }
192
193    @Override protected Executor executor() {
194      return exceptionCatchingExecutor;
195    }
196  }
197
198  public void testServiceThrowOnRun() throws Exception {
199    ThrowOnRunService service = new ThrowOnRunService();
200
201    service.startAsync();
202    try {
203      service.awaitTerminated();
204      fail();
205    } catch (IllegalStateException expected) {
206      executionThread.join();
207      assertEquals(thrownByExecutionThread, expected.getCause());
208    }
209    assertTrue(service.shutDownCalled);
210    assertEquals(Service.State.FAILED, service.state());
211    assertEquals("kaboom!", thrownByExecutionThread.getMessage());
212  }
213
214  public void testServiceThrowOnRunAndThenAgainOnShutDown() throws Exception {
215    ThrowOnRunService service = new ThrowOnRunService();
216    service.throwOnShutDown = true;
217
218    service.startAsync();
219    try {
220      service.awaitTerminated();
221      fail();
222    } catch (IllegalStateException expected) {
223      executionThread.join();
224      assertEquals(thrownByExecutionThread, expected.getCause());
225    }
226
227    assertTrue(service.shutDownCalled);
228    assertEquals(Service.State.FAILED, service.state());
229    assertEquals("kaboom!", thrownByExecutionThread.getMessage());
230  }
231
232  private class ThrowOnRunService extends AbstractExecutionThreadService {
233    private boolean shutDownCalled = false;
234    private boolean throwOnShutDown = false;
235
236    @Override protected void run() {
237      throw new UnsupportedOperationException("kaboom!");
238    }
239
240    @Override protected void shutDown() {
241      shutDownCalled = true;
242      if (throwOnShutDown) {
243        throw new UnsupportedOperationException("double kaboom!");
244      }
245    }
246
247    @Override protected Executor executor() {
248      return exceptionCatchingExecutor;
249    }
250  }
251
252  public void testServiceThrowOnShutDown() throws Exception {
253    ThrowOnShutDown service = new ThrowOnShutDown();
254
255    service.startAsync().awaitRunning();
256    assertEquals(Service.State.RUNNING, service.state());
257
258    service.stopAsync();
259    enterRun.countDown();
260    executionThread.join();
261
262    assertEquals(Service.State.FAILED, service.state());
263    assertEquals("kaboom!", thrownByExecutionThread.getMessage());
264  }
265
266  private class ThrowOnShutDown extends AbstractExecutionThreadService {
267    @Override protected void run() {
268      try {
269        enterRun.await();
270      } catch (InterruptedException e) {
271        throw new RuntimeException(e);
272      }
273    }
274
275    @Override protected void shutDown() {
276      throw new UnsupportedOperationException("kaboom!");
277    }
278
279    @Override protected Executor executor() {
280      return exceptionCatchingExecutor;
281    }
282  }
283
284  public void testServiceTimeoutOnStartUp() throws Exception {
285    TimeoutOnStartUp service = new TimeoutOnStartUp();
286
287    try {
288      service.startAsync().awaitRunning(1, TimeUnit.MILLISECONDS);
289      fail();
290    } catch (TimeoutException e) {
291      assertTrue(e.getMessage().contains(Service.State.STARTING.toString()));
292    }
293  }
294
295  private class TimeoutOnStartUp extends AbstractExecutionThreadService {
296    @Override protected Executor executor() {
297      return new Executor() {
298        @Override public void execute(Runnable command) {
299        }
300      };
301    }
302
303    @Override
304    protected void run() throws Exception {
305    }
306  }
307
308  public void testStopWhileStarting_runNotCalled() throws Exception {
309    final CountDownLatch started = new CountDownLatch(1);
310    FakeService service = new FakeService() {
311      @Override protected void startUp() throws Exception {
312        super.startUp();
313        started.await();
314      }
315    };
316    service.startAsync();
317    service.stopAsync();
318    started.countDown();
319    service.awaitTerminated();
320    assertEquals(Service.State.TERMINATED, service.state());
321    assertEquals(1, service.startupCalled);
322    assertEquals(0, service.runCalled);
323    assertEquals(1, service.shutdownCalled);
324  }
325
326  public void testStop_noStart() {
327    FakeService service = new FakeService();
328    service.stopAsync().awaitTerminated();
329    assertEquals(Service.State.TERMINATED, service.state());
330    assertEquals(0, service.startupCalled);
331    assertEquals(0, service.runCalled);
332    assertEquals(0, service.shutdownCalled);
333  }
334
335  public void testDefaultService() throws InterruptedException {
336    WaitOnRunService service = new WaitOnRunService();
337    service.startAsync().awaitRunning();
338    enterRun.await();
339    service.stopAsync().awaitTerminated();
340  }
341
342  private class FakeService extends AbstractExecutionThreadService implements TearDown {
343
344    private final ExecutorService executor = Executors.newSingleThreadExecutor();
345
346    FakeService() {
347      tearDownStack.addTearDown(this);
348    }
349
350    volatile int startupCalled = 0;
351    volatile int shutdownCalled = 0;
352    volatile int runCalled = 0;
353
354    @Override protected void startUp() throws Exception {
355      assertEquals(0, startupCalled);
356      assertEquals(0, runCalled);
357      assertEquals(0, shutdownCalled);
358      startupCalled++;
359    }
360
361    @Override protected void run() throws Exception {
362      assertEquals(1, startupCalled);
363      assertEquals(0, runCalled);
364      assertEquals(0, shutdownCalled);
365      runCalled++;
366    }
367
368    @Override protected void shutDown() throws Exception {
369      assertEquals(1, startupCalled);
370      assertEquals(0, shutdownCalled);
371      assertEquals(Service.State.STOPPING, state());
372      shutdownCalled++;
373    }
374
375    @Override protected Executor executor() {
376      return executor;
377    }
378
379    @Override public void tearDown() throws Exception {
380      executor.shutdown();
381    }
382  }
383}
384