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