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