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