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