FuturesTransformAsyncFunctionTest.java revision 1d580d0f6ee4f21eb309ba7b509d2c6d671c4044
1/*
2 * Copyright (C) 2008 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.Uninterruptibles.awaitUninterruptibly;
20
21import com.google.common.util.concurrent.ForwardingListenableFuture.SimpleForwardingListenableFuture;
22
23import java.lang.reflect.UndeclaredThrowableException;
24import java.util.concurrent.CancellationException;
25import java.util.concurrent.CountDownLatch;
26import java.util.concurrent.ExecutionException;
27
28/**
29 * Unit tests for {@link Futures#transform(ListenableFuture, AsyncFunction)}.
30 *
31 * @author Nishant Thakkar
32 */
33public class FuturesTransformAsyncFunctionTest
34    extends AbstractChainedListenableFutureTest<String> {
35  protected static final int SLOW_OUTPUT_VALID_INPUT_DATA = 2;
36  protected static final int SLOW_FUNC_VALID_INPUT_DATA = 3;
37  private static final String RESULT_DATA = "SUCCESS";
38
39  private SettableFuture<String> outputFuture;
40  // Signals that the function is waiting to complete
41  private CountDownLatch funcIsWaitingLatch;
42  // Signals the function so it will complete
43  private CountDownLatch funcCompletionLatch;
44
45  @Override protected ListenableFuture<String> buildChainingFuture(
46      ListenableFuture<Integer> inputFuture) {
47    outputFuture = SettableFuture.create();
48    funcIsWaitingLatch = new CountDownLatch(1);
49    funcCompletionLatch = new CountDownLatch(1);
50    return Futures.transform(inputFuture, new ChainingFunction());
51  }
52
53  @Override protected String getSuccessfulResult() {
54    return RESULT_DATA;
55  }
56
57  private class ChainingFunction implements AsyncFunction<Integer, String> {
58    @Override
59    public ListenableFuture<String> apply(Integer input) {
60      switch (input) {
61        case VALID_INPUT_DATA: outputFuture.set(RESULT_DATA); break;
62        case SLOW_OUTPUT_VALID_INPUT_DATA: break;  // do nothing to the result
63        case SLOW_FUNC_VALID_INPUT_DATA:
64          funcIsWaitingLatch.countDown();
65          awaitUninterruptibly(funcCompletionLatch);
66          break;
67        default: throw new UndeclaredThrowableException(EXCEPTION);
68      }
69      return outputFuture;
70    }
71  }
72
73  public void testFutureGetThrowsFunctionException() throws Exception {
74    inputFuture.set(EXCEPTION_DATA);
75    listener.assertException(EXCEPTION);
76  }
77
78  public void testFutureGetThrowsCancellationIfInputCancelled()
79      throws Exception {
80    inputFuture.cancel(true); // argument is ignored
81    try {
82      resultFuture.get();
83      fail("Result future must throw CancellationException"
84          + " if input future is cancelled.");
85    } catch (CancellationException expected) {}
86  }
87
88  public void testFutureGetThrowsCancellationIfOutputCancelled()
89      throws Exception {
90    inputFuture.set(SLOW_OUTPUT_VALID_INPUT_DATA);
91    outputFuture.cancel(true); // argument is ignored
92    try {
93      resultFuture.get();
94      fail("Result future must throw CancellationException"
95          + " if function output future is cancelled.");
96    } catch (CancellationException expected) {}
97  }
98
99  public void testFutureCancelBeforeInputCompletion() throws Exception {
100    assertTrue(resultFuture.cancel(true));
101    assertTrue(resultFuture.isCancelled());
102    assertTrue(inputFuture.isCancelled());
103    assertFalse(outputFuture.isCancelled());
104    try {
105      resultFuture.get();
106      fail("Result future is cancelled and should have thrown a"
107          + " CancellationException");
108    } catch (CancellationException expected) {}
109  }
110
111  public void testFutureCancellableBeforeOutputCompletion() throws Exception {
112    inputFuture.set(SLOW_OUTPUT_VALID_INPUT_DATA);
113    assertTrue(resultFuture.cancel(true));
114    assertTrue(resultFuture.isCancelled());
115    assertFalse(inputFuture.isCancelled());
116    assertTrue(outputFuture.isCancelled());
117    try {
118      resultFuture.get();
119      fail("Result future is cancelled and should have thrown a"
120          + " CancellationException");
121    } catch (CancellationException expected) {}
122  }
123
124  public void testFutureCancellableBeforeFunctionCompletion() throws Exception {
125    // Set the result in a separate thread since this test runs the function
126    // (which will block) in the same thread.
127    new Thread() {
128      @Override
129      public void run() {
130        inputFuture.set(SLOW_FUNC_VALID_INPUT_DATA);
131      }
132    }.start();
133    funcIsWaitingLatch.await();
134
135    assertTrue(resultFuture.cancel(true));
136    assertTrue(resultFuture.isCancelled());
137    assertFalse(inputFuture.isCancelled());
138    assertFalse(outputFuture.isCancelled());
139    try {
140      resultFuture.get();
141      fail("Result future is cancelled and should have thrown a"
142          + " CancellationException");
143    } catch (CancellationException expected) {}
144
145    funcCompletionLatch.countDown();  // allow the function to complete
146    try {
147      outputFuture.get();
148      fail("The function output future is cancelled and should have thrown a"
149          + " CancellationException");
150    } catch (CancellationException expected) {}
151  }
152
153  public void testFutureCancelAfterCompletion() throws Exception {
154    inputFuture.set(VALID_INPUT_DATA);
155    assertFalse(resultFuture.cancel(true));
156    assertFalse(resultFuture.isCancelled());
157    assertFalse(inputFuture.isCancelled());
158    assertFalse(outputFuture.isCancelled());
159    assertEquals(RESULT_DATA, resultFuture.get());
160  }
161
162  public void testFutureGetThrowsRuntimeException() throws Exception {
163    BadFuture badInput = new BadFuture(Futures.immediateFuture(20));
164    ListenableFuture<String> chain = buildChainingFuture(badInput);
165    try {
166      chain.get();
167      fail("Future.get must throw an exception when the input future fails.");
168    } catch (ExecutionException e) {
169      assertSame(RuntimeException.class, e.getCause().getClass());
170    }
171  }
172
173  /**
174   * Proxy to throw a {@link RuntimeException} out of the {@link #get()} method.
175   */
176  public static class BadFuture
177      extends SimpleForwardingListenableFuture<Integer> {
178    protected BadFuture(ListenableFuture<Integer> delegate) {
179      super(delegate);
180    }
181
182    @Override
183    public Integer get() {
184      throw new RuntimeException("Oops");
185    }
186  }
187}
188