1 /* 2 * Copyright (C) 2020 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 package android.car.test.mocks; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.car.test.util.Visitor; 21 import android.util.Log; 22 23 import com.android.car.internal.util.FunctionalUtils; 24 25 import org.mockito.invocation.InvocationOnMock; 26 import org.mockito.stubbing.Answer; 27 28 import java.util.concurrent.CountDownLatch; 29 30 /** 31 * Custom Mockito {@link Answer} that blocks until a condition is unblocked by the test case. 32 * 33 * <p>Example: 34 * 35 * <pre>{@code 36 * 37 * BlockingAnswer{@code <Void>} blockingAnswer = BlockingAnswer.forVoidReturn( 38 * 10_000, (invocation) -> { 39 * MyObject obj = (MyObject) invocation.getArguments()[0]; 40 * obj.doSomething(); 41 * }); 42 * doAnswer(blockingAnswer).when(mMock).mockSomething(); 43 * doSomethingOnTest(); 44 * blockingAnswer.unblock(); 45 * 46 * }</pre> 47 */ 48 public final class BlockingAnswer<T> implements Answer<T> { 49 50 private static final String TAG = BlockingAnswer.class.getSimpleName(); 51 52 private final CountDownLatch mLatch = new CountDownLatch(1); 53 54 private final long mTimeoutMs; 55 56 @NonNull 57 private final Visitor<InvocationOnMock> mInvocator; 58 59 @Nullable 60 private final T mValue; 61 BlockingAnswer(long timeoutMs, @NonNull Visitor<InvocationOnMock> invocator, @Nullable T value)62 private BlockingAnswer(long timeoutMs, @NonNull Visitor<InvocationOnMock> invocator, 63 @Nullable T value) { 64 mTimeoutMs = timeoutMs; 65 mInvocator = invocator; 66 mValue = value; 67 } 68 69 /** 70 * Unblocks the answer so the test case can continue. 71 */ unblock()72 public void unblock() { 73 Log.v(TAG, "Unblocking " + mLatch); 74 mLatch.countDown(); 75 } 76 77 /** 78 * Creates a {@link BlockingAnswer} for a {@code void} method. 79 * 80 * @param timeoutMs maximum time the answer would block. 81 * 82 * @param invocator code executed after the answer is unblocked. 83 */ forVoidReturn(long timeoutMs, @NonNull Visitor<InvocationOnMock> invocator)84 public static BlockingAnswer<Void> forVoidReturn(long timeoutMs, 85 @NonNull Visitor<InvocationOnMock> invocator) { 86 return new BlockingAnswer<Void>(timeoutMs, invocator, /* value= */ null); 87 } 88 89 /** 90 * Creates a {@link BlockingAnswer} for a method that returns type {@code T} 91 * 92 * @param timeoutMs maximum time the answer would block. 93 * @param invocator code executed after the answer is unblocked. 94 * 95 * @return what the answer should return 96 */ forReturn(long timeoutMs, @NonNull Visitor<InvocationOnMock> invocator, @Nullable T value)97 public static <T> BlockingAnswer<T> forReturn(long timeoutMs, 98 @NonNull Visitor<InvocationOnMock> invocator, @Nullable T value) { 99 return new BlockingAnswer<T>(timeoutMs, invocator, value); 100 } 101 102 @Override answer(InvocationOnMock invocation)103 public T answer(InvocationOnMock invocation) throws Throwable { 104 String threadName = "BlockingAnswerThread"; 105 Log.d(TAG, "Starting thread " + threadName); 106 107 new Thread(() -> { 108 JavaMockitoHelper.silentAwait(mLatch, mTimeoutMs); 109 Log.d(TAG, "Invocating " + FunctionalUtils.getLambdaName(mInvocator)); 110 mInvocator.visit(invocation); 111 }, threadName).start(); 112 Log.d(TAG, "Returning " + mValue); 113 return mValue; 114 } 115 } 116