diff options
author | Jason Monk <jmonk@google.com> | 2017-05-03 15:43:52 -0400 |
---|---|---|
committer | Jason Monk <jmonk@google.com> | 2017-05-03 15:44:18 -0400 |
commit | 0c408008f1061f9421872e4407a539688b21b79e (patch) | |
tree | 2c671d007522373c951209efd07154ab7cff15d5 /tests/testables | |
parent | 3fcdfa1b96b796de9be559e3d6c8c143fb8e3e05 (diff) |
Try to add some more documentation to testables
Test: runtest --path frameworks/base/tests/testables/tests
Change-Id: I3dc39f05c3b91929a7d37b28879e31fb699f7334
Diffstat (limited to 'tests/testables')
8 files changed, 164 insertions, 41 deletions
diff --git a/tests/testables/src/android/testing/AndroidTestingRunner.java b/tests/testables/src/android/testing/AndroidTestingRunner.java index a425f70e836c..cf5d4cf2f828 100644 --- a/tests/testables/src/android/testing/AndroidTestingRunner.java +++ b/tests/testables/src/android/testing/AndroidTestingRunner.java @@ -35,6 +35,8 @@ import java.util.List; /** * A runner with support for extra annotations provided by the Testables library. + * @see UiThreadTest + * @see TestableLooper.RunWithLooper */ public class AndroidTestingRunner extends BlockJUnit4ClassRunner { diff --git a/tests/testables/src/android/testing/BaseFragmentTest.java b/tests/testables/src/android/testing/BaseFragmentTest.java index 32ee091a46c9..5cedbdffed35 100644 --- a/tests/testables/src/android/testing/BaseFragmentTest.java +++ b/tests/testables/src/android/testing/BaseFragmentTest.java @@ -80,6 +80,10 @@ public abstract class BaseFragmentTest { }); } + /** + * Allows tests to sub-class TestableContext if they want to provide any extended functionality + * or provide a {@link LeakCheck} to the TestableContext upon instantiation. + */ protected TestableContext getContext() { return new TestableContext(InstrumentationRegistry.getContext()); } diff --git a/tests/testables/src/android/testing/LeakCheck.java b/tests/testables/src/android/testing/LeakCheck.java index 8daaa8f16664..949215b4d311 100644 --- a/tests/testables/src/android/testing/LeakCheck.java +++ b/tests/testables/src/android/testing/LeakCheck.java @@ -14,6 +14,7 @@ package android.testing; +import android.content.Context; import android.util.ArrayMap; import android.util.Log; @@ -28,6 +29,35 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +/** + * Utility for dealing with the facts of Lifecycle. Creates trackers to check that for every + * call to registerX, addX, bindX, a corresponding call to unregisterX, removeX, and unbindX + * is performed. This should be applied to a test as a {@link org.junit.rules.TestRule} + * and will only check for leaks on successful tests. + * <p> + * Example that will catch an allocation and fail: + * <pre class="prettyprint"> + * public class LeakCheckTest { + * @Rule public LeakCheck mLeakChecker = new LeakCheck(); + * + * @Test + * public void testLeak() { + * Context context = new ContextWrapper(...) { + * public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + * mLeakChecker.getTracker("receivers").addAllocation(new Throwable()); + * } + * public void unregisterReceiver(BroadcastReceiver receiver) { + * mLeakChecker.getTracker("receivers").clearAllocations(); + * } + * }; + * context.registerReceiver(...); + * } + * } + * </pre> + * + * Note: {@link TestableContext} supports leak tracking when using + * {@link TestableContext#TestableContext(Context, LeakCheck)}. + */ public class LeakCheck extends TestWatcher { private final Map<String, Tracker> mTrackers = new HashMap<>(); @@ -40,6 +70,13 @@ public class LeakCheck extends TestWatcher { verify(); } + /** + * Acquire a {@link Tracker}. Gets a tracker for the specified tag, creating one if necessary. + * There should be one tracker for each pair of add/remove callbacks (e.g. one tracker for + * registerReceiver/unregisterReceiver). + * + * @param tag Unique tag to use for this set of allocation tracking. + */ public Tracker getTracker(String tag) { Tracker t = mTrackers.get(tag); if (t == null) { @@ -49,10 +86,13 @@ public class LeakCheck extends TestWatcher { return t; } - public void verify() { + private void verify() { mTrackers.values().forEach(Tracker::verify); } + /** + * Holds allocations associated with a specific callback (such as a BroadcastReceiver). + */ public static class LeakInfo { private static final String TAG = "LeakInfo"; private List<Throwable> mThrowables = new ArrayList<>(); @@ -60,11 +100,20 @@ public class LeakCheck extends TestWatcher { LeakInfo() { } + /** + * Should be called once for each callback/listener added. addAllocation may be + * called several times, but it only takes one clearAllocations call to remove all + * of them. + */ public void addAllocation(Throwable t) { // TODO: Drop off the first element in the stack trace here to have a cleaner stack. mThrowables.add(t); } + /** + * Should be called when the callback/listener has been removed. One call to + * clearAllocations will counteract any number of calls to addAllocation. + */ public void clearAllocations() { mThrowables.clear(); } @@ -82,9 +131,16 @@ public class LeakCheck extends TestWatcher { } } + /** + * Tracks allocations related to a specific tag or method(s). + * @see #getTracker(String) + */ public static class Tracker { private Map<Object, LeakInfo> mObjects = new ArrayMap<>(); + private Tracker() { + } + public LeakInfo getLeakInfo(Object object) { LeakInfo leakInfo = mObjects.get(object); if (leakInfo == null) { diff --git a/tests/testables/src/android/testing/TestableContentResolver.java b/tests/testables/src/android/testing/TestableContentResolver.java index bfafbe043ffb..0850916ccbe5 100644 --- a/tests/testables/src/android/testing/TestableContentResolver.java +++ b/tests/testables/src/android/testing/TestableContentResolver.java @@ -27,7 +27,11 @@ import com.google.android.collect.Maps; import java.util.Map; /** - * Alternative to a MockContentResolver that falls back to real providers. + * A version of ContentResolver that allows easy mocking of providers. + * By default it acts as a normal ContentResolver and returns all the + * same providers. + * @see #addProvider(String, ContentProvider) + * @see #setFallbackToExisting(boolean) */ public class TestableContentResolver extends ContentResolver { diff --git a/tests/testables/src/android/testing/TestableContext.java b/tests/testables/src/android/testing/TestableContext.java index d6c06e47fcdf..498d517c104b 100644 --- a/tests/testables/src/android/testing/TestableContext.java +++ b/tests/testables/src/android/testing/TestableContext.java @@ -43,6 +43,7 @@ import org.junit.runners.model.Statement; * <ul> * <li>System services can be mocked out with {@link #addMockSystemService}</li> * <li>Service binding can be mocked out with {@link #addMockService}</li> + * <li>Resources can be mocked out using {@link #getOrCreateTestableResources()}</li> * <li>Settings support {@link TestableSettingsProvider}</li> * <li>Has support for {@link LeakCheck} for services and receivers</li> * </ul> @@ -50,10 +51,8 @@ import org.junit.runners.model.Statement; * <p>TestableContext should be defined as a rule on your test so it can clean up after itself. * Like the following:</p> * <pre class="prettyprint"> - * {@literal - * @Rule + * @Rule * private final TestableContext mContext = new TestableContext(InstrumentationRegister.getContext()); - * } * </pre> */ public class TestableContext extends ContextWrapper implements TestRule { @@ -132,20 +131,26 @@ public class TestableContext extends ContextWrapper implements TestRule { : super.getResources(); } + /** + * @see #getSystemService(String) + */ public <T> void addMockSystemService(Class<T> service, T mock) { addMockSystemService(getSystemServiceName(service), mock); } + /** + * @see #getSystemService(String) + */ public void addMockSystemService(String name, Object service) { if (mMockSystemServices == null) mMockSystemServices = new ArrayMap<>(); mMockSystemServices.put(name, service); } - public void addMockService(ComponentName component, IBinder service) { - if (mMockServices == null) mMockServices = new ArrayMap<>(); - mMockServices.put(component, service); - } - + /** + * If a matching mock service has been added through {@link #addMockSystemService} then + * that will be returned, otherwise the real service will be acquired from the base + * context. + */ @Override public Object getSystemService(String name) { if (mMockSystemServices != null && mMockSystemServices.containsKey(name)) { @@ -166,6 +171,10 @@ public class TestableContext extends ContextWrapper implements TestRule { return mTestableContentResolver; } + /** + * Will always return itself for a TestableContext to ensure the testable effects extend + * to the application context. + */ @Override public Context getApplicationContext() { // Return this so its always a TestableContext. @@ -199,6 +208,24 @@ public class TestableContext extends ContextWrapper implements TestRule { super.unregisterReceiver(receiver); } + /** + * Adds a mock service to be connected to by a bindService call. + * <p> + * Normally a TestableContext will pass through all bind requests to the base context + * but when addMockService has been called for a ComponentName being bound, then + * TestableContext will immediately trigger a {@link ServiceConnection#onServiceConnected} + * with the specified service, and will call {@link ServiceConnection#onServiceDisconnected} + * when the service is unbound. + * </p> + */ + public void addMockService(ComponentName component, IBinder service) { + if (mMockServices == null) mMockServices = new ArrayMap<>(); + mMockServices.put(component, service); + } + + /** + * @see #addMockService(ComponentName, IBinder) + */ @Override public boolean bindService(Intent service, ServiceConnection conn, int flags) { if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); @@ -206,6 +233,9 @@ public class TestableContext extends ContextWrapper implements TestRule { return super.bindService(service, conn, flags); } + /** + * @see #addMockService(ComponentName, IBinder) + */ @Override public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, Handler handler, UserHandle user) { @@ -214,6 +244,9 @@ public class TestableContext extends ContextWrapper implements TestRule { return super.bindServiceAsUser(service, conn, flags, handler, user); } + /** + * @see #addMockService(ComponentName, IBinder) + */ @Override public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, UserHandle user) { @@ -232,6 +265,9 @@ public class TestableContext extends ContextWrapper implements TestRule { return false; } + /** + * @see #addMockService(ComponentName, IBinder) + */ @Override public void unbindService(ServiceConnection conn) { if (mService != null) mService.getLeakInfo(conn).clearAllocations(); @@ -243,6 +279,13 @@ public class TestableContext extends ContextWrapper implements TestRule { super.unbindService(conn); } + /** + * Check if the TestableContext has a mock binding for a specified component. Will return + * true between {@link ServiceConnection#onServiceConnected} and + * {@link ServiceConnection#onServiceDisconnected} callbacks for a mock service. + * + * @see #addMockService(ComponentName, IBinder) + */ public boolean isBound(ComponentName component) { return mActiveServices != null && mActiveServices.containsValue(component); } diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index 9eddc5112d0c..f6c3cb3ec498 100644 --- a/tests/testables/src/android/testing/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -33,16 +33,15 @@ import java.lang.reflect.Field; import java.util.Map; /** - * Creates a looper on the current thread with control over if/when messages are - * executed. Warning: This class works through some reflection and may break/need - * to be updated from time to time. + * This is a wrapper around {@link TestLooperManager} to make it easier to manage + * and provide an easy annotation for use with tests. + * + * @see TestableLooperTest TestableLooperTest for examples. */ public class TestableLooper { private Looper mLooper; private MessageQueue mQueue; - private boolean mMain; - private Object mOriginalMain; private MessageHandler mMessageHandler; private Handler mHandler; @@ -72,35 +71,20 @@ public class TestableLooper { mHandler = new Handler(mLooper); } - public void setAsMainLooper() throws NoSuchFieldException, IllegalAccessException { - mMain = true; - setAsMainInt(); - } - - private void setAsMainInt() throws NoSuchFieldException, IllegalAccessException { - Field field = mLooper.getClass().getDeclaredField("sMainLooper"); - field.setAccessible(true); - if (mOriginalMain == null) { - mOriginalMain = field.get(null); - } - field.set(null, mLooper); - } - /** - * Must be called if setAsMainLooper is called to restore the main looper when the - * test is complete, otherwise the main looper will not be available for any subsequent - * tests. + * Must be called to release the looper when the test is complete, otherwise + * the looper will not be available for any subsequent tests. This is + * automatically handled for tests using {@link RunWithLooper}. */ public void destroy() throws NoSuchFieldException, IllegalAccessException { mQueueWrapper.release(); - if (mMain && mOriginalMain != null) { - Field field = mLooper.getClass().getDeclaredField("sMainLooper"); - field.setAccessible(true); - field.set(null, mOriginalMain); - mOriginalMain = null; - } } + /** + * Sets a callback for all messages processed on this TestableLooper. + * + * @see {@link MessageHandler} + */ public void setMessageHandler(MessageHandler handler) { mMessageHandler = handler; } @@ -119,6 +103,9 @@ public class TestableLooper { return num; } + /** + * Process messages in the queue until no more are found. + */ public void processAllMessages() { while (processQueuedMessages() != 0) ; } @@ -183,6 +170,11 @@ public class TestableLooper { void run() throws Exception; } + /** + * Annotation that tells the {@link AndroidTestingRunner} to create a TestableLooper and + * run this test/class on that thread. The {@link TestableLooper} can be acquired using + * {@link #get(Object)}. + */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface RunWithLooper { @@ -206,11 +198,15 @@ public class TestableLooper { private static final Map<Object, TestableLooper> sLoopers = new ArrayMap<>(); + /** + * For use with {@link RunWithLooper}, used to get the TestableLooper that was + * automatically created for this test. + */ public static TestableLooper get(Object test) { return sLoopers.get(test); } - public static class LooperFrameworkMethod extends FrameworkMethod { + static class LooperFrameworkMethod extends FrameworkMethod { private HandlerThread mHandlerThread; private final TestableLooper mTestableLooper; @@ -319,6 +315,11 @@ public class TestableLooper { } } + /** + * Callback to control the execution of messages on the looper, when set with + * {@link #setMessageHandler(MessageHandler)} then {@link #onMessageHandled(Message)} + * will get called back for every message processed on the {@link TestableLooper}. + */ public interface MessageHandler { /** * Return true to have the message executed and delivered to target. diff --git a/tests/testables/src/android/testing/UiThreadTest.java b/tests/testables/src/android/testing/UiThreadTest.java index e40e1d741f28..32a58240bae3 100644 --- a/tests/testables/src/android/testing/UiThreadTest.java +++ b/tests/testables/src/android/testing/UiThreadTest.java @@ -20,8 +20,8 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * When applied to a class, all tests, befores, and afters will behave as if - * they have @UiThreadTest applied to them. + * When applied to a class, all {@link org.junit.Test}s, {@link org.junit.After}s, and + * {@link org.junit.Before} will behave as if they have @UiThreadTest applied to them. */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) diff --git a/tests/testables/src/android/testing/ViewUtils.java b/tests/testables/src/android/testing/ViewUtils.java index 5a651aacc74f..74789989939e 100644 --- a/tests/testables/src/android/testing/ViewUtils.java +++ b/tests/testables/src/android/testing/ViewUtils.java @@ -21,8 +21,16 @@ import android.view.View; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; +/** + * Utilities to make testing views easier. + */ public class ViewUtils { + /** + * Causes the view (and its children) to have {@link View#onAttachedToWindow()} called. + * + * This is currently done by adding the view to a window. + */ public static void attachView(View view) { // Make sure hardware acceleration isn't turned on. view.getContext().getApplicationInfo().flags &= @@ -35,6 +43,11 @@ public class ViewUtils { .getSystemService(WindowManager.class).addView(view, lp); } + /** + * Causes the view (and its children) to have {@link View#onDetachedFromWindow()} called. + * + * This is currently done by removing the view from a window. + */ public static void detachView(View view) { InstrumentationRegistry.getContext() .getSystemService(WindowManager.class).removeViewImmediate(view); |