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