Merge "Try to add some more documentation to testables"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index ddd6615..27d781d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -52,7 +52,6 @@
@Before
public void setUp() throws Exception {
- TestableLooper.get(this).setAsMainLooper();
mManagers = new ArrayList<>();
QSTileHost host = new QSTileHost(mContext, null,
mock(StatusBarIconController.class));
diff --git a/tests/testables/src/android/testing/AndroidTestingRunner.java b/tests/testables/src/android/testing/AndroidTestingRunner.java
index a425f70..cf5d4cf 100644
--- a/tests/testables/src/android/testing/AndroidTestingRunner.java
+++ b/tests/testables/src/android/testing/AndroidTestingRunner.java
@@ -35,6 +35,8 @@
/**
* 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 32ee091..5cedbdf 100644
--- a/tests/testables/src/android/testing/BaseFragmentTest.java
+++ b/tests/testables/src/android/testing/BaseFragmentTest.java
@@ -80,6 +80,10 @@
});
}
+ /**
+ * 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 8daaa8f..949215b 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.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 @@
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 @@
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 @@
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 @@
}
}
+ /**
+ * 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 bfafbe0..0850916 100644
--- a/tests/testables/src/android/testing/TestableContentResolver.java
+++ b/tests/testables/src/android/testing/TestableContentResolver.java
@@ -27,7 +27,11 @@
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 d6c06e4..498d517 100644
--- a/tests/testables/src/android/testing/TestableContext.java
+++ b/tests/testables/src/android/testing/TestableContext.java
@@ -43,6 +43,7 @@
* <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 @@
* <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 @@
: 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 @@
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 @@
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 @@
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 @@
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 @@
return false;
}
+ /**
+ * @see #addMockService(ComponentName, IBinder)
+ */
@Override
public void unbindService(ServiceConnection conn) {
if (mService != null) mService.getLeakInfo(conn).clearAllocations();
@@ -243,6 +279,13 @@
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 9eddc51..f6c3cb3 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -33,16 +33,15 @@
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 @@
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 @@
return num;
}
+ /**
+ * Process messages in the queue until no more are found.
+ */
public void processAllMessages() {
while (processQueuedMessages() != 0) ;
}
@@ -183,6 +170,11 @@
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 @@
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 @@
}
}
+ /**
+ * 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 e40e1d7..32a5824 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.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 5a651aa..7478998 100644
--- a/tests/testables/src/android/testing/ViewUtils.java
+++ b/tests/testables/src/android/testing/ViewUtils.java
@@ -21,8 +21,16 @@
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 @@
.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);