• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.car.test.mocks;
17 
18 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
20 
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.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