• 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 static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
20 
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.anyString;
23 
24 import static java.lang.annotation.ElementType.METHOD;
25 import static java.lang.annotation.RetentionPolicy.RUNTIME;
26 
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.annotation.UserIdInt;
30 import android.app.ActivityManager;
31 import android.car.test.AbstractExpectableTestCase;
32 import android.os.Binder;
33 import android.os.Handler;
34 import android.os.HandlerThread;
35 import android.os.Looper;
36 import android.os.Trace;
37 import android.os.UserManager;
38 import android.util.ArraySet;
39 import android.util.Log;
40 import android.util.Slog;
41 import android.util.TimingsTraceLog;
42 
43 import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
44 import com.android.internal.util.Preconditions;
45 
46 import org.junit.After;
47 import org.junit.Before;
48 import org.junit.Rule;
49 import org.junit.rules.TestRule;
50 import org.junit.runner.Description;
51 import org.junit.runners.model.Statement;
52 import org.mockito.Mockito;
53 import org.mockito.MockitoSession;
54 import org.mockito.invocation.InvocationOnMock;
55 import org.mockito.quality.Strictness;
56 
57 import java.lang.annotation.Retention;
58 import java.lang.annotation.Target;
59 import java.lang.reflect.Constructor;
60 import java.lang.reflect.Method;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.List;
64 import java.util.Objects;
65 import java.util.Set;
66 
67 /**
68  * Base class for tests that must use {@link com.android.dx.mockito.inline.extended.ExtendedMockito}
69  * to mock static classes and final methods.
70  *
71  * <p><b>Note: </b> this class automatically spy on {@link Log} and {@link Slog} and fail tests that
72  * all any of their {@code wtf()} methods. If a test is expect to call {@code wtf()}, it should be
73  * annotated with {@link ExpectWtf}.
74  *
75  * <p><b>Note: </b>when using this class, you must include the following
76  * dependencies on {@code Android.bp} (or {@code Android.mk}:
77  * <pre><code>
78     jni_libs: [
79         "libdexmakerjvmtiagent",
80         "libstaticjvmtiagent",
81     ],
82 
83    LOCAL_JNI_SHARED_LIBRARIES := \
84       libdexmakerjvmtiagent \
85       libstaticjvmtiagent \
86  *  </code></pre>
87  */
88 public abstract class AbstractExtendedMockitoTestCase extends AbstractExpectableTestCase {
89 
90     static final String TAG = AbstractExtendedMockitoTestCase.class.getSimpleName();
91 
92     private static final boolean TRACE = false;
93 
94     private static final long SYNC_RUNNABLE_MAX_WAIT_TIME = 5_000L;
95 
96     @SuppressWarnings("IsLoggableTagLength")
97     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
98 
99     /**
100      * Should be used on constructors for test case whose object under test doesn't make any logging
101      * call.
102      */
103     protected static final String[] NO_LOG_TAGS = new String[] {
104             "I can't believe a test case is using this String as a log TAG! Well done!"
105     };
106 
107     /**
108      * Number of invocations, used to force a failure on {@link #forceFailure(int, Class, String)}.
109      */
110     private static int sInvocationsCounter;
111 
112     /**
113      * Sessions follow the "Highlander Rule": There can be only one!
114      *
115      * <p>So, we keep track of that and force-close it if needed.
116      */
117     @Nullable
118     private static MockitoSession sHighlanderSession;
119 
120     /**
121      * Points to where the current session was created.
122      */
123     private static Exception sSessionCreationLocation;
124 
125     private final List<Class<?>> mStaticSpiedClasses = new ArrayList<>();
126     private final List<Class<?>> mStaticMockedClasses = new ArrayList<>();
127 
128     // Tracks (S)Log.wtf() calls made during code execution, then used on verifyWtfNeverLogged()
129     private final List<RuntimeException> mWtfs = new ArrayList<>();
130 
131     private MockitoSession mSession;
132 
133     @Nullable
134     private final TimingsTraceLog mTracer;
135 
136     @Nullable
137     private final ArraySet<String> mLogTags;
138 
139     @Rule
140     public final WtfCheckerRule mWtfCheckerRule = new WtfCheckerRule();
141 
142     /**
143      * Default constructor.
144      *
145      * @param logTags tags to be checked for issues (like {@code wtf()} calls); use
146      * {@link #NO_LOG_TAGS} when object under test doesn't log anything.
147      */
AbstractExtendedMockitoTestCase(String... logTags)148     protected AbstractExtendedMockitoTestCase(String... logTags) {
149         Objects.requireNonNull(logTags, "logTags cannot be null");
150 
151         sInvocationsCounter++;
152 
153         if (VERBOSE) {
154             Log.v(TAG, "constructor for " + getClass() + ": sInvocationsCount="
155                     + sInvocationsCounter + ", logTags=" + Arrays.toString(logTags));
156         }
157 
158         String prefix = getClass().getSimpleName();
159         if (Arrays.equals(logTags, NO_LOG_TAGS)) {
160             if (VERBOSE) {
161                 Log.v(TAG, prefix + ": not checking for wtf logs");
162             }
163             mLogTags = null;
164         } else {
165             if (VERBOSE) {
166                 Log.v(TAG, prefix + ": checking for wtf calls on tags " + Arrays.toString(logTags));
167             }
168             mLogTags = new ArraySet<>(logTags.length);
169             for (String logTag: logTags) {
170                 mLogTags.add(logTag);
171             }
172         }
173         mTracer = TRACE ? new TimingsTraceLog(TAG, Trace.TRACE_TAG_APP) : null;
174     }
175 
176     @Before
startSession()177     public final void startSession() {
178         if (VERBOSE) {
179             Log.v(TAG, "startSession() for " + getTestName() + " on thread "
180                     + Thread.currentThread() + "; sHighlanderSession=" + sHighlanderSession);
181         }
182         finishHighlanderSessionIfNeeded("startSession()");
183 
184         beginTrace("startSession()");
185 
186         createSessionLocation();
187 
188         StaticMockitoSessionBuilder builder = mockitoSession()
189                 .strictness(getSessionStrictness());
190 
191         CustomMockitoSessionBuilder customBuilder =
192                 new CustomMockitoSessionBuilder(builder, mStaticSpiedClasses, mStaticMockedClasses)
193                     .spyStatic(Log.class)
194                     .spyStatic(Slog.class);
195 
196         beginTrace("onSessionBuilder()");
197         onSessionBuilder(customBuilder);
198         endTrace();
199 
200         if (VERBOSE) {
201             Log.v(TAG, "spied classes: " + customBuilder.mStaticSpiedClasses
202                     + " mocked classes:" + customBuilder.mStaticMockedClasses);
203         }
204 
205         beginTrace("startMocking()");
206         sHighlanderSession = mSession = builder.initMocks(this).startMocking();
207         endTrace();
208 
209         if (customBuilder.mCallback != null) {
210             if (VERBOSE) {
211                 Log.v(TAG, "Calling " + customBuilder.mCallback);
212             }
213             customBuilder.mCallback.afterSessionStarted();
214         }
215 
216         beginTrace("interceptWtfCalls()");
217         interceptWtfCalls();
218         endTrace();
219 
220         endTrace(); // startSession
221     }
222 
createSessionLocation()223     private void createSessionLocation() {
224         beginTrace("createSessionLocation()");
225         try {
226             sSessionCreationLocation = new Exception(getTestName());
227         } catch (Exception e) {
228             // Better safe than sorry...
229             Log.e(TAG, "Could not create sSessionCreationLocation with " + getTestName()
230                     + " on thread " + Thread.currentThread(), e);
231             sSessionCreationLocation = e;
232         }
233         endTrace();
234     }
235 
236     @After
finishSession()237     public final void finishSession() throws Exception {
238         if (VERBOSE) {
239             Log.v(TAG, "finishSession() for " + getTestName() + " on thread "
240                     + Thread.currentThread() + "; sHighlanderSession=" + sHighlanderSession);
241 
242         }
243         if (false) { // For obvious reasons, should NEVER be merged as true
244             forceFailure(1, RuntimeException.class, "to simulate an unfinished session");
245         }
246 
247         // mSession.finishMocking() must ALWAYS be called (hence the over-protective try/finally
248         // statements), otherwise it would cause failures on future tests as mockito
249         // cannot start a session when a previous one is not finished
250         try {
251             beginTrace("finishSession()");
252             completeAllHandlerThreadTasks();
253         } finally {
254             sHighlanderSession = null;
255             finishSessionMocking();
256         }
257         endTrace();
258     }
259 
finishSessionMocking()260     private void finishSessionMocking() {
261         if (mSession == null) {
262             Log.w(TAG, getClass().getSimpleName() + ".finishSession(): no session");
263             return;
264         }
265         try {
266             beginTrace("finishMocking()");
267         } finally {
268             try {
269                 mSession.finishMocking();
270             } finally {
271                 // Shouldn't need to set mSession to null as JUnit always instantiate a new object,
272                 // but it doesn't hurt....
273                 mSession = null;
274                 clearInlineMocks("finishMocking()");
275                 endTrace(); // finishMocking
276             }
277         }
278     }
279 
clearInlineMocks(String when)280     protected void clearInlineMocks(String when) {
281         // When using inline mock maker, clean up inline mocks to prevent OutOfMemory
282         // errors. See https://github.com/mockito/mockito/issues/1614 and b/259280359.
283         Log.d(TAG, "Calling Mockito.framework().clearInlineMocks() on " + when);
284         Mockito.framework().clearInlineMocks();
285 
286     }
287 
finishHighlanderSessionIfNeeded(String where)288     private void finishHighlanderSessionIfNeeded(String where) {
289         if (sHighlanderSession == null) {
290             if (VERBOSE) {
291                 Log.v(TAG, "finishHighlanderSessionIfNeeded(): sHighlanderSession already null");
292             }
293             return;
294         }
295 
296         beginTrace("finishHighlanderSessionIfNeeded()");
297 
298         if (sSessionCreationLocation != null) {
299             if (VERBOSE) {
300                 Log.e(TAG, where + ": There can be only one! Closing unfinished session, "
301                         + "created at", sSessionCreationLocation);
302             } else {
303                 Log.e(TAG, where + ": There can be only one! Closing unfinished session, "
304                         + "created at " +  sSessionCreationLocation);
305             }
306         } else {
307             Log.e(TAG, where + ": There can be only one! Closing unfinished session created at "
308                     + "unknown location");
309         }
310         try {
311             sHighlanderSession.finishMocking();
312         } catch (Throwable t) {
313             if (VERBOSE) {
314                 Log.e(TAG, "Failed to close unfinished session on " + getTestName(), t);
315             } else {
316                 Log.e(TAG, "Failed to close unfinished session on " + getTestName() + ": " + t);
317             }
318         } finally {
319             if (VERBOSE) {
320                 Log.v(TAG, "Resetting sHighlanderSession at finishHighlanderSessionIfNeeded()");
321             }
322             sHighlanderSession = null;
323         }
324 
325         endTrace();
326     }
327 
328     /**
329      * Forces a failure at the given invocation of a test method by throwing an exception.
330      */
forceFailure(int invocationCount, Class<T> failureClass, String reason)331     protected final <T extends Throwable> void forceFailure(int invocationCount,
332             Class<T> failureClass, String reason) throws T {
333         if (sInvocationsCounter != invocationCount) {
334             Log.d(TAG, "forceFailure(" + invocationCount + "): no-op on invocation #"
335                     + sInvocationsCounter);
336             return;
337         }
338         String message = "Throwing on invocation #" + sInvocationsCounter + ": " + reason;
339         Log.e(TAG, message);
340         T throwable;
341         try {
342             Constructor<T> constructor = failureClass.getConstructor(String.class);
343             throwable = constructor.newInstance("Throwing on invocation #" + sInvocationsCounter
344                     + ": " + reason);
345         } catch (Exception e) {
346             throw new IllegalArgumentException("Could not create exception of class " + failureClass
347                     + " using msg='" + message + "' as constructor");
348         }
349         throw throwable;
350     }
351 
352     /**
353      * Gets the name of the test being run.
354      */
getTestName()355     protected final String getTestName() {
356         return mWtfCheckerRule.mTestName;
357     }
358 
359     /**
360      * Waits for completion of all pending Handler tasks for all HandlerThread in the process.
361      *
362      * <p>This can prevent pending Handler tasks of one test from affecting another. This does not
363      * work if the message is posted with delay.
364      */
completeAllHandlerThreadTasks()365     protected final void completeAllHandlerThreadTasks() {
366         beginTrace("completeAllHandlerThreadTasks");
367         Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
368         ArrayList<HandlerThread> handlerThreads = new ArrayList<>(threadSet.size());
369         Thread currentThread = Thread.currentThread();
370         for (Thread t : threadSet) {
371             if (t != currentThread && t instanceof HandlerThread) {
372                 if (VERBOSE) {
373                     Log.v(TAG, "Will wait for " + t);
374                 }
375                 handlerThreads.add((HandlerThread) t);
376             } else if (VERBOSE) {
377                 Log.v(TAG, "Skipping " + t);
378             }
379         }
380         int size = handlerThreads.size();
381         ArrayList<SyncRunnable> syncs = new ArrayList<>(size);
382         Log.d(TAG, "Waiting for " + size + " HandlerThreads");
383         for (int i = 0; i < size; i++) {
384             HandlerThread thread = handlerThreads.get(i);
385             Looper looper = thread.getLooper();
386             if (looper == null) {
387                 Log.w(TAG, "Ignoring thread " + thread + ". It doesn't have a looper.");
388                 continue;
389             }
390             if (VERBOSE) {
391                 Log.v(TAG, "Waiting for thread " + thread);
392             }
393             Handler handler = new Handler(looper);
394             SyncRunnable sr = new SyncRunnable(() -> { });
395             handler.post(sr);
396             syncs.add(sr);
397         }
398         beginTrace("waitForComplete");
399         for (int i = 0; i < syncs.size(); i++) {
400             syncs.get(i).waitForComplete(SYNC_RUNNABLE_MAX_WAIT_TIME);
401         }
402         endTrace(); // waitForComplete
403         endTrace(); // completeAllHandlerThreadTasks
404     }
405 
406     /**
407      * Subclasses can use this method to initialize the Mockito session that's started before every
408      * test on {@link #startSession()}.
409      *
410      * <p>Typically, it should be overridden when mocking static methods.
411      *
412      * <p><b>NOTE:</b> you don't need to call it to spy on {@link Log} or {@link Slog}, as those
413      * are already spied on.
414      */
onSessionBuilder(@onNull CustomMockitoSessionBuilder session)415     protected void onSessionBuilder(@NonNull CustomMockitoSessionBuilder session) {
416         if (VERBOSE) Log.v(TAG, getLogPrefix() + "onSessionBuilder()");
417     }
418 
419     /**
420      * Changes the value of the session created by
421      * {@link #onSessionBuilder(CustomMockitoSessionBuilder)}.
422      *
423      * <p>By default it's set to {@link Strictness.LENIENT}, but subclasses can overwrite this
424      * method to change the behavior.
425      */
426     @NonNull
getSessionStrictness()427     protected Strictness getSessionStrictness() {
428         return Strictness.LENIENT;
429     }
430 
431     /**
432      * Mocks a call to {@link ActivityManager#getCurrentUser()}.
433      *
434      * @param userId result of such call
435      *
436      * @throws IllegalStateException if class didn't override
437      * {@link #onSessionBuilder(CustomMockitoSessionBuilder)} and called
438      * {@code spyStatic(Binder.class)} on the session passed to it.
439      */
mockGetCurrentUser(@serIdInt int userId)440     protected final void mockGetCurrentUser(@UserIdInt int userId) {
441         if (VERBOSE) Log.v(TAG, getLogPrefix() + "mockGetCurrentUser(" + userId + ")");
442         assertSpied(ActivityManager.class);
443 
444         beginTrace("mockAmGetCurrentUser-" + userId);
445         AndroidMockitoHelper.mockAmGetCurrentUser(userId);
446         endTrace();
447     }
448 
449     /**
450      * Mocks a call to {@link UserManager#isHeadlessSystemUserMode()}.
451      *
452      * @param mode result of such call
453      *
454      * @throws IllegalStateException if class didn't override
455      * {@link #onSessionBuilder(CustomMockitoSessionBuilder)} and called
456      * {@code spyStatic(Binder.class)} on the session passed to it.
457      */
mockIsHeadlessSystemUserMode(boolean mode)458     protected final void mockIsHeadlessSystemUserMode(boolean mode) {
459         if (VERBOSE) Log.v(TAG, getLogPrefix() + "mockIsHeadlessSystemUserMode(" + mode + ")");
460         assertSpied(UserManager.class);
461 
462         beginTrace("mockUmIsHeadlessSystemUserMode");
463         AndroidMockitoHelper.mockUmIsHeadlessSystemUserMode(mode);
464         endTrace();
465     }
466 
467     /**
468      * Mocks a call to {@link Binder.getCallingUserHandle()}.
469      *
470      * @throws IllegalStateException if class didn't override
471      * {@link #onSessionBuilder(CustomMockitoSessionBuilder)} and called
472      * {@code spyStatic(Binder.class)} on the session passed to it.
473      */
mockGetCallingUserHandle(@serIdInt int userId)474     protected final void mockGetCallingUserHandle(@UserIdInt int userId) {
475         if (VERBOSE) Log.v(TAG, getLogPrefix() + "mockBinderCallingUser(" + userId + ")");
476         assertSpied(Binder.class);
477 
478         beginTrace("mockBinderCallingUser");
479         AndroidMockitoHelper.mockBinderGetCallingUserHandle(userId);
480         endTrace();
481     }
482 
483     /**
484      * Starts a tracing message.
485      *
486      * <p>MUST be followed by a {@link #endTrace()} calls.
487      *
488      * <p>Ignored if {@value #VERBOSE} is {@code false}.
489      */
beginTrace(@onNull String message)490     protected final void beginTrace(@NonNull String message) {
491         if (mTracer == null) return;
492 
493         Log.d(TAG, getLogPrefix() + message);
494         mTracer.traceBegin(message);
495     }
496 
497     /**
498      * Ends a tracing call.
499      *
500      * <p>MUST be called after {@link #beginTrace(String)}.
501      *
502      * <p>Ignored if {@value #VERBOSE} is {@code false}.
503      */
endTrace()504     protected final void endTrace() {
505         if (mTracer == null) return;
506 
507         mTracer.traceEnd();
508     }
509 
interceptWtfCalls()510     private void interceptWtfCalls() {
511         try {
512             doAnswer((invocation) -> {
513                 return addWtf(invocation);
514             }).when(() -> Log.wtf(anyString(), anyString()));
515             doAnswer((invocation) -> {
516                 return addWtf(invocation);
517             }).when(() -> Log.wtf(anyString(), any(Throwable.class)));
518             doAnswer((invocation) -> {
519                 return addWtf(invocation);
520             }).when(() -> Log.wtf(anyString(), anyString(), any(Throwable.class)));
521             doAnswer((invocation) -> {
522                 return addWtf(invocation);
523             }).when(() -> Slog.wtf(anyString(), anyString()));
524             doAnswer((invocation) -> {
525                 return addWtf(invocation);
526             }).when(() -> Slog.wtf(anyString(), any(Throwable.class)));
527             doAnswer((invocation) -> {
528                 return addWtf(invocation);
529             }).when(() -> Slog.wtf(anyString(), anyString(), any(Throwable.class)));
530             // NOTE: android.car.builtin.util.Slogf calls android.util.Slog behind the scenes, so no
531             // need to check for calls of the former...
532         } catch (Throwable t) {
533             Log.e(TAG, "interceptWtfCalls(): failed for test " + getTestName(), t);
534         }
535     }
536 
addWtf(InvocationOnMock invocation)537     private Object addWtf(InvocationOnMock invocation) {
538         String message = "Called " + invocation;
539         Log.d(TAG, message); // Log always, as some test expect it
540         String actualTag = (String) invocation.getArguments()[0];
541         if (mLogTags != null && mLogTags.contains(actualTag)) {
542             mWtfs.add(new IllegalStateException(message));
543         } else if (VERBOSE) {
544             Log.v(TAG, "ignoring WTF invocation on tag " + actualTag + ". mLogTags=" + mLogTags);
545         }
546         return null;
547     }
548 
verifyWtfLogged()549     private void verifyWtfLogged() {
550         Preconditions.checkState(!mWtfs.isEmpty(), "no wtf() called");
551     }
552 
verifyWtfNeverLogged()553     private void verifyWtfNeverLogged() {
554         int size = mWtfs.size();
555         if (VERBOSE) {
556             Log.v(TAG, "verifyWtfNeverLogged(): mWtfs=" + mWtfs);
557         }
558 
559         switch (size) {
560             case 0:
561                 return;
562             case 1:
563                 throw mWtfs.get(0);
564             default:
565                 StringBuilder msg = new StringBuilder("wtf called ").append(size).append(" times")
566                         .append(": ").append(mWtfs);
567                 throw new AssertionError(msg.toString());
568         }
569     }
570 
571     /**
572      * Gets a prefix for {@link Log} calls
573      */
getLogPrefix()574     protected final String getLogPrefix() {
575         return getClass().getSimpleName() + ".";
576     }
577 
578     /**
579      * Asserts the given class is being spied in the Mockito session.
580      */
assertSpied(Class<?> clazz)581     protected final void assertSpied(Class<?> clazz) {
582         Preconditions.checkArgument(mStaticSpiedClasses.contains(clazz),
583                 "did not call spyStatic() on %s", clazz.getName());
584     }
585 
586     /**
587      * Asserts the given class is being mocked in the Mockito session.
588      */
assertMocked(Class<?> clazz)589     protected final void assertMocked(Class<?> clazz) {
590         Preconditions.checkArgument(mStaticMockedClasses.contains(clazz),
591                 "did not call mockStatic() on %s", clazz.getName());
592     }
593 
594     /**
595      * Custom {@code MockitoSessionBuilder} used to make sure some pre-defined mock expectations
596      * (like {@link AbstractExtendedMockitoTestCase#mockGetCurrentUser(int)} fail if the test case
597      * didn't explicitly set it to spy / mock the required classes.
598      *
599      * <p><b>NOTE: </b>for now it only provides simple {@link #spyStatic(Class)}, but more methods
600      * (as provided by {@link StaticMockitoSessionBuilder}) could be provided as needed.
601      */
602     public static final class CustomMockitoSessionBuilder {
603         private final StaticMockitoSessionBuilder mBuilder;
604         private final List<Class<?>> mStaticSpiedClasses;
605         private final List<Class<?>> mStaticMockedClasses;
606 
607         private @Nullable SessionCallback mCallback;
608 
CustomMockitoSessionBuilder(StaticMockitoSessionBuilder builder, List<Class<?>> staticSpiedClasses, List<Class<?>> staticMockedClasses)609         private CustomMockitoSessionBuilder(StaticMockitoSessionBuilder builder,
610                 List<Class<?>> staticSpiedClasses, List<Class<?>> staticMockedClasses) {
611             mBuilder = builder;
612             mStaticSpiedClasses = staticSpiedClasses;
613             mStaticMockedClasses = staticMockedClasses;
614         }
615 
616         /**
617          * Same as {@link StaticMockitoSessionBuilder#spyStatic(Class)}.
618          */
spyStatic(Class<T> clazz)619         public <T> CustomMockitoSessionBuilder spyStatic(Class<T> clazz) {
620             Preconditions.checkState(!mStaticSpiedClasses.contains(clazz),
621                     "already called spyStatic() on " + clazz);
622             mStaticSpiedClasses.add(clazz);
623             mBuilder.spyStatic(clazz);
624             return this;
625         }
626 
627         /**
628          * Same as {@link StaticMockitoSessionBuilder#mockStatic(Class)}.
629          */
mockStatic(Class<T> clazz)630         public <T> CustomMockitoSessionBuilder mockStatic(Class<T> clazz) {
631             Preconditions.checkState(!mStaticMockedClasses.contains(clazz),
632                     "already called mockStatic() on " + clazz);
633             mStaticMockedClasses.add(clazz);
634             mBuilder.mockStatic(clazz);
635             return this;
636         }
637 
setSessionCallback(SessionCallback callback)638         void setSessionCallback(SessionCallback callback) {
639             mCallback = callback;
640         }
641     }
642 
643     // TODO(b/156033195): only used by MockSettings, should go away if that class is refactored to
644     // not mock stuff
645     interface SessionCallback {
afterSessionStarted()646         void afterSessionStarted();
647     }
648 
649     private final class WtfCheckerRule implements TestRule {
650 
651         @Nullable
652         private String mTestName;
653 
654         @Override
apply(Statement base, Description description)655         public Statement apply(Statement base, Description description) {
656             return new Statement() {
657                 @Override
658                 public void evaluate() throws Throwable {
659                     mTestName = description.getDisplayName();
660                     String testMethodName = description.getMethodName();
661                     if (VERBOSE) Log.v(TAG, "running " + mTestName);
662 
663                     Method testMethod = AbstractExtendedMockitoTestCase.this.getClass()
664                             .getMethod(testMethodName);
665                     ExpectWtf expectWtfAnnotation = testMethod.getAnnotation(ExpectWtf.class);
666                     Preconditions.checkState(expectWtfAnnotation == null || mLogTags != null,
667                             "Must call constructor that pass logTags on %s to use @%s",
668                             description.getTestClass(), ExpectWtf.class.getSimpleName());
669 
670                     beginTrace("evaluate-" + testMethodName);
671                     base.evaluate();
672                     endTrace();
673 
674                     beginTrace("verify-wtfs");
675                     try {
676                         if (expectWtfAnnotation != null) {
677                             if (VERBOSE) Log.v(TAG, "expecting wtf()");
678                             verifyWtfLogged();
679                         } else {
680                             if (VERBOSE) Log.v(TAG, "NOT expecting wtf()");
681                             verifyWtfNeverLogged();
682                         }
683                     } finally {
684                         endTrace();
685                     }
686                 }
687             };
688         }
689     }
690 
691     /**
692      * Annotation used on test methods that are expect to call {@code wtf()} methods on {@link Log}
693      * or {@link Slog} - if such methods are not annotated with this annotation, they will fail.
694      */
695     @Retention(RUNTIME)
696     @Target({METHOD})
697     public static @interface ExpectWtf {
698     }
699 
700     private static final class SyncRunnable implements Runnable {
701         private final Runnable mTarget;
702         private volatile boolean mComplete = false;
703 
704         private SyncRunnable(Runnable target) {
705             mTarget = target;
706         }
707 
708         @Override
709         public void run() {
710             mTarget.run();
711             synchronized (this) {
712                 mComplete = true;
713                 notifyAll();
714             }
715         }
716 
717         private void waitForComplete(long maxWaitTime) {
718             long t0 = System.currentTimeMillis();
719             synchronized (this) {
720                 while (!mComplete && System.currentTimeMillis() - t0 < maxWaitTime) {
721                     try {
722                         wait();
723                     } catch (InterruptedException e) {
724                         Thread.currentThread().interrupt();
725                         throw new IllegalStateException("Interrupted SyncRunnable thread", e);
726                     }
727                 }
728             }
729         }
730     }
731 }
732