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.base.Throwables;
20
21import junit.framework.TestCase;
22
23import java.lang.Thread.UncaughtExceptionHandler;
24import java.util.concurrent.CountDownLatch;
25import java.util.concurrent.ExecutionException;
26import java.util.concurrent.Executor;
27import java.util.concurrent.Future;
28import java.util.concurrent.TimeUnit;
29import java.util.concurrent.TimeoutException;
30
31/**
32 * Unit test for {@link AbstractExecutionThreadService}.
33 *
34 * @author Jesse Wilson
35 */
36public class AbstractExecutionThreadServiceTest extends TestCase {
37
38  private final CountDownLatch enterRun = new CountDownLatch(1);
39  private final CountDownLatch exitRun = new CountDownLatch(1);
40
41  private Thread executionThread;
42  private Throwable thrownByExecutionThread;
43  private final Executor executor = new Executor() {
44    @Override
45    public void execute(Runnable command) {
46      executionThread = new Thread(command);
47      executionThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
48        @Override
49        public void uncaughtException(Thread thread, Throwable e) {
50          thrownByExecutionThread = e;
51        }
52      });
53      executionThread.start();
54    }
55  };
56
57  public void testServiceStartStop() throws Exception {
58    WaitOnRunService service = new WaitOnRunService();
59    assertFalse(service.startUpCalled);
60
61    service.start().get();
62    assertTrue(service.startUpCalled);
63    assertEquals(Service.State.RUNNING, service.state());
64
65    enterRun.await(); // to avoid stopping the service until run() is invoked
66
67    service.stop().get();
68    assertTrue(service.shutDownCalled);
69    assertEquals(Service.State.TERMINATED, service.state());
70    executionThread.join();
71    assertNull(thrownByExecutionThread);
72  }
73
74  public void testServiceStartStopIdempotence() throws Exception {
75    WaitOnRunService service = new WaitOnRunService();
76
77    service.start();
78    service.start();
79    service.startAndWait();
80    assertEquals(Service.State.RUNNING, service.state());
81    service.startAndWait();
82    assertEquals(Service.State.RUNNING, service.state());
83
84    enterRun.await(); // to avoid stopping the service until run() is invoked
85
86    service.stop();
87    service.stop();
88    service.stopAndWait();
89    assertEquals(Service.State.TERMINATED, service.state());
90    service.stopAndWait();
91    assertEquals(Service.State.TERMINATED, service.state());
92
93    assertEquals(Service.State.RUNNING, service.start().get());
94    assertEquals(Service.State.RUNNING, service.startAndWait());
95    assertEquals(Service.State.TERMINATED, service.stop().get());
96    assertEquals(Service.State.TERMINATED, service.stopAndWait());
97
98    executionThread.join();
99    assertNull(thrownByExecutionThread);
100  }
101
102  public void testServiceExitingOnItsOwn() throws Exception {
103    WaitOnRunService service = new WaitOnRunService();
104    service.expectedShutdownState = Service.State.RUNNING;
105
106    service.start().get();
107    assertTrue(service.startUpCalled);
108    assertEquals(Service.State.RUNNING, service.state());
109
110    exitRun.countDown(); // the service will exit voluntarily
111    executionThread.join();
112
113    assertTrue(service.shutDownCalled);
114    assertEquals(Service.State.TERMINATED, service.state());
115    assertNull(thrownByExecutionThread);
116
117    service.stop().get(); // no-op
118    assertEquals(Service.State.TERMINATED, service.state());
119    assertTrue(service.shutDownCalled);
120  }
121
122  private class WaitOnRunService extends AbstractExecutionThreadService {
123    private boolean startUpCalled = false;
124    private boolean runCalled = false;
125    private boolean shutDownCalled = false;
126    private State expectedShutdownState = State.STOPPING;
127
128    @Override protected void startUp() {
129      assertFalse(startUpCalled);
130      assertFalse(runCalled);
131      assertFalse(shutDownCalled);
132      startUpCalled = true;
133      assertEquals(State.STARTING, state());
134    }
135
136    @Override protected void run() {
137      assertTrue(startUpCalled);
138      assertFalse(runCalled);
139      assertFalse(shutDownCalled);
140      runCalled = true;
141      assertEquals(State.RUNNING, state());
142
143      enterRun.countDown();
144      try {
145        exitRun.await();
146      } catch (InterruptedException e) {
147        throw Throwables.propagate(e);
148      }
149    }
150
151    @Override protected void shutDown() {
152      assertTrue(startUpCalled);
153      assertTrue(runCalled);
154      assertFalse(shutDownCalled);
155      shutDownCalled = true;
156      assertEquals(expectedShutdownState, state());
157    }
158
159    @Override protected void triggerShutdown() {
160      exitRun.countDown();
161    }
162
163    @Override protected Executor executor() {
164      return executor;
165    }
166  }
167
168  public void testServiceThrowOnStartUp() throws Exception {
169    ThrowOnStartUpService service = new ThrowOnStartUpService();
170    assertFalse(service.startUpCalled);
171
172    Future<Service.State> startupFuture = service.start();
173    try {
174      startupFuture.get();
175      fail();
176    } catch (ExecutionException expected) {
177      assertEquals("kaboom!", expected.getCause().getMessage());
178    }
179    executionThread.join();
180
181    assertTrue(service.startUpCalled);
182    assertEquals(Service.State.FAILED, service.state());
183    assertTrue(thrownByExecutionThread.getMessage().equals("kaboom!"));
184  }
185
186  private class ThrowOnStartUpService extends AbstractExecutionThreadService {
187    private boolean startUpCalled = false;
188
189    @Override protected void startUp() {
190      startUpCalled = true;
191      throw new UnsupportedOperationException("kaboom!");
192    }
193
194    @Override protected void run() {
195      throw new AssertionError("run() should not be called");
196    }
197
198    @Override protected Executor executor() {
199      return executor;
200    }
201  }
202
203  public void testServiceThrowOnRun() throws Exception {
204    ThrowOnRunService service = new ThrowOnRunService();
205
206    service.start().get();
207
208    executionThread.join();
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.start().get();
219    executionThread.join();
220
221    assertTrue(service.shutDownCalled);
222    assertEquals(Service.State.FAILED, service.state());
223    assertEquals("kaboom!", thrownByExecutionThread.getMessage());
224  }
225
226  private class ThrowOnRunService extends AbstractExecutionThreadService {
227    private boolean shutDownCalled = false;
228    private boolean throwOnShutDown = false;
229
230    @Override protected void run() {
231      throw new UnsupportedOperationException("kaboom!");
232    }
233
234    @Override protected void shutDown() {
235      shutDownCalled = true;
236      if (throwOnShutDown) {
237        throw new UnsupportedOperationException("double kaboom!");
238      }
239    }
240
241    @Override protected Executor executor() {
242      return executor;
243    }
244  }
245
246  public void testServiceThrowOnShutDown() throws Exception {
247    ThrowOnShutDown service = new ThrowOnShutDown();
248
249    service.start().get();
250    assertEquals(Service.State.RUNNING, service.state());
251
252    service.stop();
253    enterRun.countDown();
254    executionThread.join();
255
256    assertEquals(Service.State.FAILED, service.state());
257    assertEquals("kaboom!", thrownByExecutionThread.getMessage());
258  }
259
260  private class ThrowOnShutDown extends AbstractExecutionThreadService {
261    @Override protected void run() {
262      try {
263        enterRun.await();
264      } catch (InterruptedException e) {
265        throw Throwables.propagate(e);
266      }
267    }
268
269    @Override protected void shutDown() {
270      throw new UnsupportedOperationException("kaboom!");
271    }
272
273    @Override protected Executor executor() {
274      return executor;
275    }
276  }
277
278  public void testServiceTimeoutOnStartUp() throws Exception {
279    TimeoutOnStartUp service = new TimeoutOnStartUp();
280
281    try {
282      service.start().get(1, TimeUnit.MILLISECONDS);
283      fail();
284    } catch (TimeoutException e) {
285      assertTrue(e.getMessage().contains(Service.State.STARTING.toString()));
286    }
287  }
288
289  private class TimeoutOnStartUp extends AbstractExecutionThreadService {
290    @Override protected Executor executor() {
291      return new Executor() {
292        @Override public void execute(Runnable command) {
293        }
294      };
295    }
296
297    @Override
298    protected void run() throws Exception {
299    }
300  }
301
302}
303