• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
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 
17 package android.car.util.concurrent;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.assertThrows;
22 
23 import static java.util.concurrent.TimeUnit.MILLISECONDS;
24 
25 import android.os.Parcel;
26 import android.platform.test.annotations.DisabledOnRavenwood;
27 
28 import org.junit.Before;
29 import org.junit.Test;
30 
31 import java.util.concurrent.CountDownLatch;
32 import java.util.concurrent.ExecutionException;
33 import java.util.concurrent.TimeoutException;
34 import java.util.function.BiFunction;
35 
36 public final class AndroidFutureTest {
37     private static final String STRING_VALUE = "test-future-string";
38     private static final String EXCEPTION_MESSAGE = "An exception was thrown!";
39     private static final long TIMEOUT_MS = 100;
40 
41     private AndroidFuture<String> mUncompletedFuture;
42     private AndroidFuture<String> mCompletedFuture;
43 
44     private CountDownLatch mLatch = new CountDownLatch(1);
45     private Parcel mParcel = Parcel.obtain();
46 
47     @Before
setup()48     public void setup() {
49         mUncompletedFuture = new AndroidFuture<>();
50         mCompletedFuture = AndroidFuture.completedFuture(STRING_VALUE);
51     }
52 
53     @Test
testComplete_uncompleted()54     public void testComplete_uncompleted() throws Exception {
55         boolean changed = mUncompletedFuture.complete(STRING_VALUE);
56 
57         assertThat(changed).isTrue();
58         assertThat(mUncompletedFuture.get()).isEqualTo(STRING_VALUE);
59     }
60 
61     @Test
testComplete_alreadyCompleted()62     public void testComplete_alreadyCompleted() throws Exception {
63         AndroidFuture<String> completedFuture = AndroidFuture.completedFuture(STRING_VALUE);
64 
65         boolean changed = completedFuture.complete(STRING_VALUE);
66 
67         assertThat(changed).isFalse();
68         assertThat(completedFuture.get()).isEqualTo(STRING_VALUE);
69     }
70 
71     @Test
testCompleteExceptionally_uncompleted()72     public void testCompleteExceptionally_uncompleted() throws Exception {
73         Exception origException = new UnsupportedOperationException();
74         boolean changed = mUncompletedFuture.completeExceptionally(origException);
75 
76         assertThat(changed).isTrue();
77         ExecutionException thrown = assertThrows(ExecutionException.class,
78                 () -> mUncompletedFuture.get());
79         assertThat(thrown.getCause()).isSameInstanceAs(origException);
80     }
81 
82     @Test
testCompleteExceptionally_alreadyCompleted()83     public void testCompleteExceptionally_alreadyCompleted() throws Exception {
84         boolean changed = mCompletedFuture.completeExceptionally(
85                 new RuntimeException("throw this"));
86 
87         assertThat(changed).isFalse();
88         assertThat(mCompletedFuture.get()).isEqualTo(STRING_VALUE);
89     }
90 
91     @Test
testCancel_uncompleted()92     public void testCancel_uncompleted() throws Exception {
93         boolean changed = mUncompletedFuture.cancel(/* mayInterruptIfRunning= */true);
94 
95         assertThat(changed).isTrue();
96     }
97 
98     @Test
testCancel_alreadyCompleted()99     public void testCancel_alreadyCompleted() throws Exception {
100         boolean changed = mCompletedFuture.cancel(/* mayInterruptIfRunning= */true);
101 
102         assertThat(changed).isFalse();
103         assertThat(mCompletedFuture.get()).isEqualTo(STRING_VALUE);
104     }
105 
106     @SuppressWarnings("FutureReturnValueIgnored")
107     @Test
testWhenComplete_alreadyCompleted()108     public void testWhenComplete_alreadyCompleted() throws Exception {
109         mCompletedFuture.whenComplete((obj, err) -> {
110             assertThat(obj).isEqualTo(STRING_VALUE);
111             assertThat(err).isNull();
112             mLatch.countDown();
113         });
114         mLatch.await();
115     }
116 
117     @SuppressWarnings("FutureReturnValueIgnored")
118     @Test
testWhenComplete_uncompleted()119     public void testWhenComplete_uncompleted() throws Exception {
120         mUncompletedFuture.whenComplete((obj, err) -> {
121             assertThat(obj).isEqualTo(STRING_VALUE);
122             assertThat(err).isNull();
123             mLatch.countDown();
124         });
125         assertThat(mLatch.getCount()).isEqualTo(1);
126         mUncompletedFuture.complete(STRING_VALUE);
127         mLatch.await();
128         assertThat(mLatch.getCount()).isEqualTo(0);
129     }
130 
131     @SuppressWarnings("FutureReturnValueIgnored")
132     @Test
testWhenComplete_completeExceptionally()133     public void testWhenComplete_completeExceptionally() throws Exception {
134         Exception origException = new UnsupportedOperationException(EXCEPTION_MESSAGE);
135         mUncompletedFuture.completeExceptionally(origException);
136 
137         mUncompletedFuture.whenComplete((obj, err) -> {
138             assertThat(obj).isNull();
139             assertThat(err).isSameInstanceAs(origException);
140             mLatch.countDown();
141         });
142         mLatch.await();
143     }
144 
145     @Test
testWhenComplete_nullAction()146     public void testWhenComplete_nullAction() throws Exception {
147         assertThrows(NullPointerException.class,
148                 () -> mUncompletedFuture.whenComplete(/* action= */null));
149     }
150 
151     @Test
testWhenCompleteAsync_nullExecutor()152     public void testWhenCompleteAsync_nullExecutor() throws Exception {
153         assertThrows(NullPointerException.class,
154                 () -> mUncompletedFuture.whenCompleteAsync((o, e) -> {}, /* executor= */null));
155     }
156 
157     @SuppressWarnings("FutureReturnValueIgnored")
158     @Test
testOrTimeout_completed()159     public void testOrTimeout_completed() throws Exception {
160         mCompletedFuture.orTimeout(TIMEOUT_MS, MILLISECONDS);
161 
162         assertThat(mCompletedFuture.get()).isEqualTo(STRING_VALUE);
163     }
164 
165     @SuppressWarnings("FutureReturnValueIgnored")
166     @Test
testOrTimeout_uncompleted_timesOut()167     public void testOrTimeout_uncompleted_timesOut() throws Exception {
168         mUncompletedFuture.orTimeout(TIMEOUT_MS, MILLISECONDS);
169 
170         Throwable exception = assertThrows(Exception.class,
171                 () -> mUncompletedFuture.get(TIMEOUT_MS * 2, MILLISECONDS));
172 
173         // In most cases, an ExecutionException is thrown with its cause set to a TimingException,
174         // or depending on the timing just a TimingException is thrown. Should handle both case to
175         // avoid test flakyness.
176         if (exception instanceof ExecutionException) {
177             exception = exception.getCause();
178         }
179 
180         assertThat(exception).isInstanceOf(TimeoutException.class);
181     }
182 
183     @Test
testSetTimeoutHandler_nullHandler()184     public void testSetTimeoutHandler_nullHandler() throws Exception {
185         assertThrows(NullPointerException.class,
186                 () -> mUncompletedFuture.setTimeoutHandler(/* h= */null));
187     }
188 
189     @Test
testWriteToParcel_completed()190     public void testWriteToParcel_completed() throws Exception {
191         Parcel parcel = Parcel.obtain();
192         mCompletedFuture.writeToParcel(parcel, 0);
193 
194         parcel.setDataPosition(0);
195         AndroidFuture fromParcel = AndroidFuture.CREATOR.createFromParcel(parcel);
196 
197         assertThat(fromParcel.get()).isEqualTo(STRING_VALUE);
198     }
199 
200     @Test
testWriteToParcel_completedExceptionally()201     public void testWriteToParcel_completedExceptionally() throws Exception {
202         AndroidFuture<Integer> original = new AndroidFuture<>();
203         UnsupportedOperationException exception = new UnsupportedOperationException(
204                 EXCEPTION_MESSAGE);
205         original.completeExceptionally(exception);
206         original.writeToParcel(mParcel, /* flags = */0);
207 
208         mParcel.setDataPosition(0);
209         AndroidFuture fromParcel = AndroidFuture.CREATOR.createFromParcel(mParcel);
210 
211         ExecutionException thrown = assertThrows(ExecutionException.class, () -> fromParcel.get());
212         assertThat(thrown.getCause()).isInstanceOf(UnsupportedOperationException.class);
213         assertThat(thrown.getMessage()).contains(EXCEPTION_MESSAGE);
214     }
215 
216     @DisabledOnRavenwood(reason = "android.os.Parcel.writeStronBinder not supported")
217     @Test
testWriteToParcel_uncompleted()218     public void testWriteToParcel_uncompleted() throws Exception {
219         mUncompletedFuture.writeToParcel(mParcel, /* flags= */0);
220 
221         mParcel.setDataPosition(0);
222         AndroidFuture fromParcel = AndroidFuture.CREATOR.createFromParcel(mParcel);
223 
224         fromParcel.complete(STRING_VALUE);
225         assertThat(mUncompletedFuture.get()).isEqualTo(STRING_VALUE);
226     }
227 
228     @DisabledOnRavenwood(reason = "android.os.Parcel.writeStronBinder not supported")
229     @Test
testWriteToParcel_uncompleted_Exception()230     public void testWriteToParcel_uncompleted_Exception() throws Exception {
231         mUncompletedFuture.writeToParcel(mParcel, /* flags= */0);
232 
233         mParcel.setDataPosition(0);
234         AndroidFuture fromParcel = AndroidFuture.CREATOR.createFromParcel(mParcel);
235         UnsupportedOperationException exception = new UnsupportedOperationException(
236                 EXCEPTION_MESSAGE);
237         fromParcel.completeExceptionally(exception);
238         ExecutionException thrown =
239                 assertThrows(ExecutionException.class, () -> mUncompletedFuture.get());
240         assertThat(thrown.getCause()).isSameInstanceAs(exception);
241     }
242 
243     @Test
testSupply()244     public void testSupply() throws Exception {
245         AndroidFuture<String> suppliedFuture = AndroidFuture.supply(() -> STRING_VALUE);
246 
247         assertThat(suppliedFuture.get()).isEqualTo(STRING_VALUE);
248     }
249 
250     @Test
testSupply_futureThrowingException()251     public void testSupply_futureThrowingException() throws Exception {
252         UnsupportedOperationException exception = new UnsupportedOperationException(
253                 EXCEPTION_MESSAGE);
254         AndroidFuture<String> future = AndroidFuture.supply(() -> {
255             throw exception;
256         });
257 
258         ExecutionException thrown = assertThrows(ExecutionException.class, () -> future.get());
259 
260         assertThat(thrown.getCause()).isSameInstanceAs(exception);
261     }
262 
263     @Test
testThenApply()264     public void testThenApply() throws Exception {
265         String appendString = " future is here";
266         AndroidFuture<String> farFuture = mUncompletedFuture.thenApply(s -> s + appendString);
267 
268         mUncompletedFuture.complete(STRING_VALUE);
269         String expectedResult = STRING_VALUE + appendString;
270 
271         assertThat(farFuture.get()).isEqualTo(expectedResult);
272     }
273 
274     @Test
testThenApply_functionThrowingException()275     public void testThenApply_functionThrowingException() throws Exception {
276         UnsupportedOperationException exception = new UnsupportedOperationException(
277                 EXCEPTION_MESSAGE);
278         AndroidFuture<String> farFuture = mUncompletedFuture.thenApply(s -> {
279             throw exception;
280         });
281 
282         mUncompletedFuture.complete(STRING_VALUE);
283         ExecutionException thrown = assertThrows(ExecutionException.class, () -> farFuture.get());
284 
285         assertThat(thrown.getCause()).isSameInstanceAs(exception);
286     }
287 
288     @Test
testThenCompose()289     public void testThenCompose() throws Exception {
290         String appendString = " future is here";
291         AndroidFuture<String> composedFuture = mUncompletedFuture.thenCompose(
292                 s -> AndroidFuture.supply(() -> s + appendString));
293 
294         mUncompletedFuture.complete(STRING_VALUE);
295         String expectedResult = STRING_VALUE + appendString;
296 
297         assertThat(composedFuture.get()).isEqualTo(expectedResult);
298     }
299 
300     @Test
testThenCompose_functionThrowingException()301     public void testThenCompose_functionThrowingException() throws Exception {
302         UnsupportedOperationException exception = new UnsupportedOperationException(
303                 EXCEPTION_MESSAGE);
304         AndroidFuture<String> throwingFuture = AndroidFuture.supply(() -> {
305             throw exception;
306         });
307         AndroidFuture<String> composedFuture = mUncompletedFuture.thenCompose(s -> throwingFuture);
308 
309         mUncompletedFuture.complete(STRING_VALUE);
310         ExecutionException thrown = assertThrows(ExecutionException.class,
311                 () -> composedFuture.get());
312 
313         assertThat(thrown.getCause()).isSameInstanceAs(exception);
314     }
315 
316     @Test
testThenCombine()317     public void testThenCombine() throws Exception {
318         String nearFutureString = "near future comes";
319         AndroidFuture<String> nearFuture = AndroidFuture.supply(() -> nearFutureString);
320         String farFutureString = " before far future.";
321         AndroidFuture<String> farFuture = AndroidFuture.supply(() -> farFutureString);
322         AndroidFuture<String> combinedFuture =
323                 nearFuture.thenCombine(farFuture, ((s1, s2) -> s1 + s2));
324 
325         assertThat(combinedFuture.get()).isEqualTo(nearFutureString + farFutureString);
326     }
327 
328     @Test
testThenCombine_functionThrowingException()329     public void testThenCombine_functionThrowingException() throws Exception {
330         String nearFutureString = "near future comes";
331         AndroidFuture<String> nearFuture = AndroidFuture.supply(() -> nearFutureString);
332         String farFutureString = " before far future.";
333         AndroidFuture<String> farFuture = AndroidFuture.supply(() -> farFutureString);
334         UnsupportedOperationException exception = new UnsupportedOperationException(
335                 EXCEPTION_MESSAGE);
336         BiFunction<String, String, String> throwingFunction = (s1, s2) -> {
337             throw exception;
338         };
339         AndroidFuture<String> combinedFuture = nearFuture.thenCombine(farFuture, throwingFunction);
340 
341         ExecutionException thrown = assertThrows(ExecutionException.class,
342                 () -> combinedFuture.get());
343 
344         assertThat(thrown.getCause()).isSameInstanceAs(exception);
345     }
346 }
347