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