• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.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<Void> blockingAnswer = BlockingAnswer.forVoidReturn(10_000, (invocation) -> {
38  *     MyObject obj = (MyObject) invocation.getArguments()[0];
39  *     obj.doSomething();
40  *  });
41  *  doAnswer(blockingAnswer).when(mMock).mockSomething();
42  *  doSomethingOnTest();
43  *  blockingAnswer.unblock();
44  *
45  * </code></pre>
46  */
47 public final class BlockingAnswer<T> implements Answer<T> {
48 
49     private static final String TAG = BlockingAnswer.class.getSimpleName();
50 
51     private final CountDownLatch mLatch = new CountDownLatch(1);
52 
53     private final long mTimeoutMs;
54 
55     @NonNull
56     private final Visitor<InvocationOnMock> mInvocator;
57 
58     @Nullable
59     private final T mValue;
60 
BlockingAnswer(long timeoutMs, @NonNull Visitor<InvocationOnMock> invocator, @Nullable T value)61     private BlockingAnswer(long timeoutMs, @NonNull Visitor<InvocationOnMock> invocator,
62             @Nullable T value) {
63         mTimeoutMs = timeoutMs;
64         mInvocator = invocator;
65         mValue = value;
66     }
67 
68     /**
69      * Unblocks the answer so the test case can continue.
70      */
unblock()71     public void unblock() {
72         Log.v(TAG, "Unblocking " + mLatch);
73         mLatch.countDown();
74     }
75 
76     /**
77      * Creates a {@link BlockingAnswer} for a {@code void} method.
78      *
79      * @param timeoutMs maximum time the answer would block.
80      *
81      * @param invocator code executed after the answer is unblocked.
82      */
forVoidReturn(long timeoutMs, @NonNull Visitor<InvocationOnMock> invocator)83     public static BlockingAnswer<Void> forVoidReturn(long timeoutMs,
84             @NonNull Visitor<InvocationOnMock> invocator) {
85         return new BlockingAnswer<Void>(timeoutMs, invocator, /* value= */ null);
86     }
87 
88     /**
89      * Creates a {@link BlockingAnswer} for a method that returns type {@code T}
90      *
91      * @param timeoutMs maximum time the answer would block.
92      * @param invocator code executed after the answer is unblocked.
93      *
94      * @return what the answer should return
95      */
forReturn(long timeoutMs, @NonNull Visitor<InvocationOnMock> invocator, @Nullable T value)96     public static <T> BlockingAnswer<T> forReturn(long timeoutMs,
97             @NonNull Visitor<InvocationOnMock> invocator, @Nullable T value) {
98         return new BlockingAnswer<T>(timeoutMs, invocator, value);
99     }
100 
101     @Override
answer(InvocationOnMock invocation)102     public T answer(InvocationOnMock invocation) throws Throwable {
103         String threadName = "BlockingAnswerThread";
104         Log.d(TAG, "Starting thread " + threadName);
105 
106         new Thread(() -> {
107             JavaMockitoHelper.silentAwait(mLatch, mTimeoutMs);
108             Log.d(TAG, "Invocating " + FunctionalUtils.getLambdaName(mInvocator));
109             mInvocator.visit(invocation);
110         }, threadName).start();
111         Log.d(TAG, "Returning " + mValue);
112         return mValue;
113     }
114 }
115