Bug 1260478 - Part 1: Duplicate TestFennecTabsStorage integration test into TestTabsProvider unit test. r?mcomella draft
authorNick Alexander <nalexander@mozilla.com>
Sun, 27 Mar 2016 14:20:41 -0700
changeset 345550 11af26b2434fc083169a2a71d8abd816ced3ca1b
parent 345549 e56e76ec40cb0e8f11278a5e61414972b771e9fd
child 345551 1fb7bcfe5cd13b4139c5253daf7a3f01a331b43b
push id14106
push usernalexander@mozilla.com
push dateTue, 29 Mar 2016 17:26:05 +0000
reviewersmcomella
bugs1260478
milestone48.0a1
Bug 1260478 - Part 1: Duplicate TestFennecTabsStorage integration test into TestTabsProvider unit test. r?mcomella Notes: * Setting the package name in robolectric.properties lets us read resources. If we don't, Robolectric tries to read from org.mozilla.fennec_$USER or similar. * We need DelegatingTestContentProvider not for isolation but to append "test=1" to all URIs. Robolectric provides isolation by starting each test in a clean environment, but if we don't tell the CP to run in test mode, it tries to write into DBs that Robolectric doesn't like. * Robolectric needs manual "shimming", i.e. the test must tell the ShadowContentResolver how to resolve. We also need to handle shutdown() ourselves. Basically, Robolectric doesn't try to duplicate the entire Android ContentProvider lifecycle. * We might grow a "ContentProviderTest" base class to handle the registration and shutdown in the future. I find such base classes frustrating and limiting in our Robocop tests, so I'd like to try to avoid them in our unit tests for as long as possible. MozReview-Commit-ID: A0paQXA2uoy
mobile/android/base/java/org/mozilla/gecko/db/SharedBrowserDatabaseProvider.java
mobile/android/tests/background/junit4/resources/robolectric.properties
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/db/DelegatingTestContentProvider.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/db/TestTabsProvider.java
--- a/mobile/android/base/java/org/mozilla/gecko/db/SharedBrowserDatabaseProvider.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/SharedBrowserDatabaseProvider.java
@@ -33,17 +33,17 @@ public abstract class SharedBrowserDatab
 
     private static PerProfileDatabases<BrowserDatabaseHelper> databases;
 
     @Override
     protected PerProfileDatabases<BrowserDatabaseHelper> getDatabases() {
         return databases;
     }
 
-    // Can't mark as @Override. Added in API 11.
+    @Override
     public void shutdown() {
         synchronized (SharedBrowserDatabaseProvider.class) {
             databases.shutdown();
             databases = null;
         }
     }
 
     @Override
--- a/mobile/android/tests/background/junit4/resources/robolectric.properties
+++ b/mobile/android/tests/background/junit4/resources/robolectric.properties
@@ -1,2 +1,3 @@
 sdk=21
 constants=org.mozilla.gecko.BuildConfig
+packageName=org.mozilla.gecko
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/db/DelegatingTestContentProvider.java
@@ -0,0 +1,87 @@
+package org.mozilla.gecko.background.db;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.net.Uri;
+
+import org.mozilla.gecko.db.BrowserContract;
+
+import java.util.ArrayList;
+
+/**
+ * Wrap a ContentProvider, appending &test=1 to all queries.
+ */
+public class DelegatingTestContentProvider extends ContentProvider {
+    protected final ContentProvider mTargetProvider;
+
+    protected static Uri appendUriParam(Uri uri, String param, String value) {
+        return uri.buildUpon().appendQueryParameter(param, value).build();
+    }
+
+    public DelegatingTestContentProvider(ContentProvider targetProvider) {
+        super();
+        mTargetProvider = targetProvider;
+    }
+
+    private Uri appendTestParam(Uri uri) {
+        try {
+            return appendUriParam(uri, BrowserContract.PARAM_IS_TEST, "1");
+        } catch (Exception e) {
+        }
+
+        return null;
+    }
+
+    @Override
+    public boolean onCreate() {
+        return mTargetProvider.onCreate();
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return mTargetProvider.getType(uri);
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return mTargetProvider.delete(appendTestParam(uri), selection, selectionArgs);
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return mTargetProvider.insert(appendTestParam(uri), values);
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection,
+                      String[] selectionArgs) {
+        return mTargetProvider.update(appendTestParam(uri), values,
+                selection, selectionArgs);
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+                        String[] selectionArgs, String sortOrder) {
+        return mTargetProvider.query(appendTestParam(uri), projection, selection,
+                selectionArgs, sortOrder);
+    }
+
+    @Override
+    public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
+            throws OperationApplicationException {
+        return mTargetProvider.applyBatch(operations);
+    }
+
+    @Override
+    public int bulkInsert(Uri uri, ContentValues[] values) {
+        return mTargetProvider.bulkInsert(appendTestParam(uri), values);
+    }
+
+    public ContentProvider getTargetProvider() {
+        return mTargetProvider;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/db/TestTabsProvider.java
@@ -0,0 +1,249 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.gecko.background.db;
+
+import android.content.ContentProviderClient;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+
+import org.json.simple.JSONArray;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.db.TabsProvider;
+import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
+import org.robolectric.shadows.ShadowContentResolver;
+
+@RunWith(TestRunner.class)
+public class TestTabsProvider {
+    public static final String TEST_CLIENT_GUID = "test guid"; // Real GUIDs never contain spaces.
+    public static final String TEST_CLIENT_NAME = "test client name";
+
+    public static final String CLIENTS_GUID_IS = BrowserContract.Clients.GUID + " = ?";
+    public static final String TABS_CLIENT_GUID_IS = BrowserContract.Tabs.CLIENT_GUID + " = ?";
+
+    protected Tab testTab1;
+    protected Tab testTab2;
+    protected Tab testTab3;
+
+    protected TabsProvider provider;
+
+    @Before
+    public void setUp() {
+        provider = new TabsProvider();
+        provider.onCreate();
+        ShadowContentResolver.registerProvider(BrowserContract.TABS_AUTHORITY, new DelegatingTestContentProvider(provider));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        provider.shutdown();
+        provider = null;
+    }
+
+    protected ContentProviderClient getClientsClient() {
+        final ShadowContentResolver cr = new ShadowContentResolver();
+        return cr.acquireContentProviderClient(BrowserContractHelpers.CLIENTS_CONTENT_URI);
+    }
+
+    protected ContentProviderClient getTabsClient() {
+        final ShadowContentResolver cr = new ShadowContentResolver();
+        return cr.acquireContentProviderClient(BrowserContractHelpers.TABS_CONTENT_URI);
+    }
+
+    protected int deleteTestClient(final ContentProviderClient clientsClient) throws RemoteException {
+        if (clientsClient == null) {
+            return -1;
+        }
+        return clientsClient.delete(BrowserContractHelpers.CLIENTS_CONTENT_URI, CLIENTS_GUID_IS, new String[] { TEST_CLIENT_GUID });
+    }
+
+    protected int deleteAllTestTabs(final ContentProviderClient tabsClient) throws RemoteException {
+        if (tabsClient == null) {
+            return -1;
+        }
+        return tabsClient.delete(BrowserContractHelpers.TABS_CONTENT_URI, TABS_CLIENT_GUID_IS, new String[]{TEST_CLIENT_GUID});
+    }
+
+    protected void insertTestClient(final ContentProviderClient clientsClient) throws RemoteException {
+        ContentValues cv = new ContentValues();
+        cv.put(BrowserContract.Clients.GUID, TEST_CLIENT_GUID);
+        cv.put(BrowserContract.Clients.NAME, TEST_CLIENT_NAME);
+        clientsClient.insert(BrowserContractHelpers.CLIENTS_CONTENT_URI, cv);
+    }
+
+    @SuppressWarnings("unchecked")
+    protected void insertSomeTestTabs(ContentProviderClient tabsClient) throws RemoteException {
+        final JSONArray history1 = new JSONArray();
+        history1.add("http://test.com/test1.html");
+        testTab1 = new Tab("test title 1", "http://test.com/test1.png", history1, 1000);
+
+        final JSONArray history2 = new JSONArray();
+        history2.add("http://test.com/test2.html#1");
+        history2.add("http://test.com/test2.html#2");
+        history2.add("http://test.com/test2.html#3");
+        testTab2 = new Tab("test title 2", "http://test.com/test2.png", history2, 2000);
+
+        final JSONArray history3 = new JSONArray();
+        history3.add("http://test.com/test3.html#1");
+        history3.add("http://test.com/test3.html#2");
+        testTab3 = new Tab("test title 3", "http://test.com/test3.png", history3, 3000);
+
+        tabsClient.insert(BrowserContractHelpers.TABS_CONTENT_URI, testTab1.toContentValues(TEST_CLIENT_GUID, 0));
+        tabsClient.insert(BrowserContractHelpers.TABS_CONTENT_URI, testTab2.toContentValues(TEST_CLIENT_GUID, 1));
+        tabsClient.insert(BrowserContractHelpers.TABS_CONTENT_URI, testTab3.toContentValues(TEST_CLIENT_GUID, 2));
+    }
+
+    // Sanity.
+    @Test
+    public void testObtainCP() {
+        final ContentProviderClient clientsClient = getClientsClient();
+        Assert.assertNotNull(clientsClient);
+        clientsClient.release();
+
+        final ContentProviderClient tabsClient = getTabsClient();
+        Assert.assertNotNull(tabsClient);
+        tabsClient.release();
+    }
+
+    @Test
+    public void testWipeClients() throws RemoteException {
+        final Uri uri = BrowserContractHelpers.CLIENTS_CONTENT_URI;
+        final ContentProviderClient clientsClient = getClientsClient();
+
+        // Have to ensure that it's empty…
+        clientsClient.delete(uri, null, null);
+
+        int deleted = clientsClient.delete(uri, null, null);
+        Assert.assertEquals(0, deleted);
+    }
+
+    @Test
+    public void testWipeTabs() throws RemoteException {
+        final ContentProviderClient tabsClient = getTabsClient();
+
+        // Have to ensure that it's empty…
+        deleteAllTestTabs(tabsClient);
+
+        int deleted = deleteAllTestTabs(tabsClient);
+        Assert.assertEquals(0, deleted);
+    }
+
+    @Test
+    public void testStoreAndRetrieveClients() throws RemoteException {
+        final Uri uri = BrowserContractHelpers.CLIENTS_CONTENT_URI;
+        final ContentProviderClient clientsClient = getClientsClient();
+
+        // Have to ensure that it's empty…
+        clientsClient.delete(uri, null, null);
+
+        final long now = System.currentTimeMillis();
+        final ContentValues first = new ContentValues();
+        final ContentValues second = new ContentValues();
+        first.put(BrowserContract.Clients.GUID, "abcdefghijkl");
+        first.put(BrowserContract.Clients.NAME, "Frist Psot");
+        first.put(BrowserContract.Clients.LAST_MODIFIED, now + 1);
+        second.put(BrowserContract.Clients.GUID, "mnopqrstuvwx");
+        second.put(BrowserContract.Clients.NAME, "Second!!1!");
+        second.put(BrowserContract.Clients.LAST_MODIFIED, now + 2);
+
+        ContentValues[] values = new ContentValues[] { first, second };
+        final int inserted = clientsClient.bulkInsert(uri, values);
+        Assert.assertEquals(2, inserted);
+
+        final String since = BrowserContract.Clients.LAST_MODIFIED + " >= ?";
+        final String[] nowArg = new String[] { String.valueOf(now) };
+        final String guidAscending = BrowserContract.Clients.GUID + " ASC";
+        Cursor cursor = clientsClient.query(uri, null, since, nowArg, guidAscending);
+
+        Assert.assertNotNull(cursor);
+        try {
+            Assert.assertTrue(cursor.moveToFirst());
+            Assert.assertEquals(2, cursor.getCount());
+
+            final String g1 = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Clients.GUID));
+            final String n1 = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Clients.NAME));
+            final long m1   = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Clients.LAST_MODIFIED));
+            Assert.assertEquals(first.get(BrowserContract.Clients.GUID), g1);
+            Assert.assertEquals(first.get(BrowserContract.Clients.NAME), n1);
+            Assert.assertEquals(now + 1, m1);
+
+            Assert.assertTrue(cursor.moveToNext());
+            final String g2 = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Clients.GUID));
+            final String n2 = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Clients.NAME));
+            final long m2   = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Clients.LAST_MODIFIED));
+            Assert.assertEquals(second.get(BrowserContract.Clients.GUID), g2);
+            Assert.assertEquals(second.get(BrowserContract.Clients.NAME), n2);
+            Assert.assertEquals(now + 2, m2);
+
+            Assert.assertFalse(cursor.moveToNext());
+        } finally {
+            cursor.close();
+        }
+
+        int deleted = clientsClient.delete(uri, null, null);
+        Assert.assertEquals(2, deleted);
+    }
+
+    @Test
+    public void testTabFromCursor() throws Exception {
+        final ContentProviderClient tabsClient = getTabsClient();
+        final ContentProviderClient clientsClient = getClientsClient();
+
+        deleteAllTestTabs(tabsClient);
+        deleteTestClient(clientsClient);
+        insertTestClient(clientsClient);
+        insertSomeTestTabs(tabsClient);
+
+        final String positionAscending = BrowserContract.Tabs.POSITION + " ASC";
+        Cursor cursor = null;
+        try {
+            cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null, TABS_CLIENT_GUID_IS, new String[] { TEST_CLIENT_GUID }, positionAscending);
+            Assert.assertEquals(3, cursor.getCount());
+
+            cursor.moveToFirst();
+            final Tab parsed1 = Tab.fromCursor(cursor);
+            Assert.assertEquals(testTab1, parsed1);
+
+            cursor.moveToNext();
+            final Tab parsed2 = Tab.fromCursor(cursor);
+            Assert.assertEquals(testTab2, parsed2);
+
+            cursor.moveToPosition(2);
+            final Tab parsed3 = Tab.fromCursor(cursor);
+            Assert.assertEquals(testTab3, parsed3);
+        } finally {
+            cursor.close();
+        }
+    }
+
+    @Test
+    public void testDeletingClientDeletesTabs() throws Exception {
+        final ContentProviderClient tabsClient = getTabsClient();
+        final ContentProviderClient clientsClient = getClientsClient();
+
+        deleteAllTestTabs(tabsClient);
+        deleteTestClient(clientsClient);
+        insertTestClient(clientsClient);
+        insertSomeTestTabs(tabsClient);
+
+        // Delete just the client...
+        clientsClient.delete(BrowserContractHelpers.CLIENTS_CONTENT_URI, CLIENTS_GUID_IS, new String [] { TEST_CLIENT_GUID });
+
+        Cursor cursor = null;
+        try {
+            cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null, TABS_CLIENT_GUID_IS, new String[] { TEST_CLIENT_GUID }, null);
+            // ... and all that client's tabs should be removed.
+            Assert.assertEquals(0, cursor.getCount());
+        } finally {
+            cursor.close();
+        }
+    }
+}