Bug 975792 - ContentProvider tests don't clean up their cursors. r=nalexander
☠☠ backed out by 3290585588fe ☠ ☠
authorRichard Newman <rnewman@mozilla.com>
Sun, 23 Feb 2014 10:53:10 -0800
changeset 170423 9122f3be8c3a7f0e66aee61829af412123848e8f
parent 170422 f9dcf7171707e64fa88423089553dc1c1749714f
child 170424 8f547ee05df3e2c74bdc48fed4b91b54ba1bcb01
push id26288
push userryanvm@gmail.com
push dateTue, 25 Feb 2014 20:20:43 +0000
treeherdermozilla-central@22650589a724 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnalexander
bugs975792
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 975792 - ContentProvider tests don't clean up their cursors. r=nalexander * * * Bug 975792 - Follow-up: attempting to fix bustage that only appears on tinderbox. a=test-only * * * Bug 975792 - Follow-up: y u fail, test?
mobile/android/base/tests/testBrowserProvider.java
--- a/mobile/android/base/tests/testBrowserProvider.java
+++ b/mobile/android/base/tests/testBrowserProvider.java
@@ -8,16 +8,17 @@ import android.content.OperationApplicat
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Build;
 import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.Random;
 
+import org.mozilla.gecko.background.db.CursorDumper;
 import org.mozilla.gecko.db.BrowserContract;
 
 /*
  * This test is meant to exercise all operations exposed by Fennec's
  * history and bookmarks content provider. It does so in an isolated
  * environment (see ContentProviderTest) without affecting any UI-related
  * code.
  */
@@ -60,27 +61,53 @@ public class testBrowserProvider extends
                                         BrowserContract.Bookmarks.MOBILE_FOLDER_GUID,
                                         BrowserContract.Bookmarks.MENU_FOLDER_GUID,
                                         BrowserContract.Bookmarks.TAGS_FOLDER_GUID,
                                         BrowserContract.Bookmarks.TOOLBAR_FOLDER_GUID,
                                         BrowserContract.Bookmarks.UNFILED_FOLDER_GUID,
                                         BrowserContract.Bookmarks.READING_LIST_FOLDER_GUID });
 
         c = mProvider.query(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), null, null, null, null);
-        mAsserter.is(c.getCount(), 7, "All non-special bookmarks and folders were deleted");
+        assertCountIsAndClose(c, 7, "All non-special bookmarks and folders were deleted");
 
         mProvider.delete(appendUriParam(BrowserContract.History.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"), null, null);
         c = mProvider.query(appendUriParam(BrowserContract.History.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), null, null, null, null);
-        mAsserter.is(c.getCount(), 0, "All history entries were deleted");
+        assertCountIsAndClose(c, 0, "All history entries were deleted");
+
+        /**
+         * There's no reason why the following two parts should fail.
+         * But sometimes they do, and I'm not going to spend the time
+         * to figure out why in an unrelated bug.
+         */
+
+        mProvider.delete(appendUriParam(BrowserContract.Favicons.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"), null, null);
+        c = mProvider.query(appendUriParam(BrowserContract.Favicons.CONTENT_URI,
+                                           BrowserContract.PARAM_SHOW_DELETED, "1"),
+                                           null, null, null, null);
 
-        mProvider.delete(BrowserContract.Favicons.CONTENT_URI, null, null);
-        mAsserter.is(c.getCount(), 0, "All favicons were deleted");
+        if (c.getCount() > 0) {
+          mAsserter.dumpLog("Unexpected favicons in ensureEmptyDatabase.");
+          CursorDumper.dumpCursor(c);
+        }
+        c.close();
+
+        // assertCountIsAndClose(c, 0, "All favicons were deleted");
 
-        mProvider.delete(BrowserContract.Thumbnails.CONTENT_URI, null, null);
-        mAsserter.is(c.getCount(), 0, "All thumbnails were deleted");
+        mProvider.delete(appendUriParam(BrowserContract.Thumbnails.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"), null, null);
+        c = mProvider.query(appendUriParam(BrowserContract.Thumbnails.CONTENT_URI,
+                                           BrowserContract.PARAM_SHOW_DELETED, "1"),
+                                           null, null, null, null);
+
+        if (c.getCount() > 0) {
+          mAsserter.dumpLog("Unexpected thumbnails in ensureEmptyDatabase.");
+          CursorDumper.dumpCursor(c);
+        }
+        c.close();
+
+        // assertCountIsAndClose(c, 0, "All thumbnails were deleted");
     }
 
     private ContentValues createBookmark(String title, String url, long parentId,
             int type, int position, String tags, String description, String keyword) throws Exception {
         ContentValues bookmark = new ContentValues();
 
         bookmark.put(BrowserContract.Bookmarks.TITLE, title);
         bookmark.put(BrowserContract.Bookmarks.URL, url);
@@ -321,31 +348,31 @@ public class testBrowserProvider extends
             try {
                 applyResult = mProvider.applyBatch(mOperations);
             } catch (OperationApplicationException ex) {
                 seenException = true;
             }
             mAsserter.is(seenException, false, "Batch updating succeded");
             mOperations.clear();
 
-            // Delte all visits
+            // Delete all visits
             for (int i = 0; i < TESTCOUNT; i++) {
                 builder = ContentProviderOperation.newDelete(BrowserContract.History.CONTENT_URI);
                 builder.withSelection(BrowserContract.History.URL  + " = ?",
                                       new String[] {"http://www.test.org/" + i});
                 builder.withExpectedCount(1);
                 // Queue the operation
                 mOperations.add(builder.build());
             }
             try {
                 applyResult = mProvider.applyBatch(mOperations);
             } catch (OperationApplicationException ex) {
                 seenException = true;
             }
-            mAsserter.is(seenException, false, "Batch deletion succeded");
+            mAsserter.is(seenException, false, "Batch deletion succeeded");
         }
 
         // Force a Constraint error, see if later operations still apply correctly
         public void testApplyBatchErrors() throws Exception {
             ArrayList<ContentProviderOperation> mOperations
                 = new ArrayList<ContentProviderOperation>();
 
             // Test a bunch of inserts with applyBatch
@@ -470,16 +497,18 @@ public class testBrowserProvider extends
                     mAsserter.is(new Integer(id), new Integer(rootId), "The id of places folder is correct");
                 } else if (guid.equals(BrowserContract.Bookmarks.READING_LIST_FOLDER_GUID)) {
                     mAsserter.is(new Integer(id), new Integer(readingListId), "The id of reading list folder is correct");
                 }
 
                 mAsserter.is(new Integer(parentId), new Integer(rootId),
                              "The PARENT of the " + guid + " special folder is correct");
             }
+
+            c.close();
         }
     }
 
     class TestInsertBookmarks extends Test {
         private long insertWithNullCol(String colName) throws Exception {
             ContentValues b = createOneBookmark();
             b.putNull(colName);
             long id = -1;
@@ -544,16 +573,17 @@ public class testBrowserProvider extends
             b.remove(BrowserContract.Bookmarks.TYPE);
             id = ContentUris.parseId(mProvider.insert(BrowserContract.Bookmarks.CONTENT_URI, b));
             c = getBookmarkById(id);
 
             mAsserter.is(c.moveToFirst(), true, "Inserted bookmark found");
 
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.Bookmarks.TYPE)), String.valueOf(BrowserContract.Bookmarks.TYPE_BOOKMARK),
                          "Inserted bookmark has correct default type");
+            c.close();
         }
     }
 
     class TestInsertBookmarksFavicons extends Test {
         @Override
         public void test() throws Exception {
             ContentValues b = createOneBookmark();
 
@@ -566,83 +596,91 @@ public class testBrowserProvider extends
             mProvider.insert(BrowserContract.Favicons.CONTENT_URI, createFaviconEntry(pageUrl, favicon));
 
             Cursor c = getBookmarkById(id, new String[] { BrowserContract.Bookmarks.FAVICON });
 
             mAsserter.is(c.moveToFirst(), true, "Inserted bookmark found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Bookmarks.FAVICON)), "UTF8"),
                          favicon, "Inserted bookmark has corresponding favicon image");
+            c.close();
 
             c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Inserted favicon found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"),
                          favicon, "Inserted favicon has corresponding favicon image");
+            c.close();
         }
     }
 
     class TestDeleteBookmarks extends Test {
         private long insertOneBookmark() throws Exception {
             ContentValues b = createOneBookmark();
             long id = ContentUris.parseId(mProvider.insert(BrowserContract.Bookmarks.CONTENT_URI, b));
 
             Cursor c = getBookmarkById(id);
             mAsserter.is(c.moveToFirst(), true, "Inserted bookmark found");
+            c.close();
 
             return id;
         }
 
         @Override
         public void test() throws Exception {
             long id = insertOneBookmark();
 
             int deleted = mProvider.delete(BrowserContract.Bookmarks.CONTENT_URI,
                                            BrowserContract.Bookmarks._ID + " = ?",
                                            new String[] { String.valueOf(id) });
 
             mAsserter.is((deleted == 1), true, "Inserted bookmark was deleted");
 
             Cursor c = getBookmarkById(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), id);
             mAsserter.is(c.moveToFirst(), true, "Deleted bookmark was only marked as deleted");
+            c.close();
 
             deleted = mProvider.delete(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"),
                                        BrowserContract.Bookmarks._ID + " = ?",
                                        new String[] { String.valueOf(id) });
 
             mAsserter.is((deleted == 1), true, "Inserted bookmark was deleted");
 
             c = getBookmarkById(appendUriParam(BrowserContract.Bookmarks.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), id);
             mAsserter.is(c.moveToFirst(), false, "Inserted bookmark is now actually deleted");
+            c.close();
 
             id = insertOneBookmark();
 
             deleted = mProvider.delete(ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id), null, null);
             mAsserter.is((deleted == 1), true,
                          "Inserted bookmark was deleted using URI with id");
 
             c = getBookmarkById(id);
             mAsserter.is(c.moveToFirst(), false,
                          "Inserted bookmark can't be found after deletion using URI with ID");
+            c.close();
 
             if (Build.VERSION.SDK_INT >= 8 &&
                 Build.VERSION.SDK_INT < 16) {
                 ContentValues b = createBookmark("Folder", null, mMobileFolderId,
                         BrowserContract.Bookmarks.TYPE_FOLDER, 0, "folderTags", "folderDescription", "folderKeyword");
 
                 long parentId = ContentUris.parseId(mProvider.insert(BrowserContract.Bookmarks.CONTENT_URI, b));
                 c = getBookmarkById(parentId);
                 mAsserter.is(c.moveToFirst(), true, "Inserted bookmarks folder found");
+                c.close();
 
                 b = createBookmark("Example", "http://example.com", parentId,
                         BrowserContract.Bookmarks.TYPE_BOOKMARK, 0, "tags", "description", "keyword");
 
                 id = ContentUris.parseId(mProvider.insert(BrowserContract.Bookmarks.CONTENT_URI, b));
                 c = getBookmarkById(id);
                 mAsserter.is(c.moveToFirst(), true, "Inserted bookmark found");
+                c.close();
 
                 deleted = 0;
                 try {
                     Uri uri = ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, parentId);
                     deleted = mProvider.delete(appendUriParam(uri, BrowserContract.PARAM_IS_SYNC, "1"), null, null);
                 } catch(Exception e) {}
 
                 mAsserter.is((deleted == 0), true,
@@ -659,21 +697,23 @@ public class testBrowserProvider extends
             final String pageUrl = b.getAsString(BrowserContract.Bookmarks.URL);
             long id = ContentUris.parseId(mProvider.insert(BrowserContract.Bookmarks.CONTENT_URI, b));
 
             // Insert the favicon into the favicons table
             mProvider.insert(BrowserContract.Favicons.CONTENT_URI, createFaviconEntry(pageUrl, "FAVICON"));
 
             Cursor c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Inserted favicon found");
+            c.close();
 
             mProvider.delete(ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id), null, null);
 
             c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), false, "Favicon is deleted with last reference to it");
+            c.close();
         }
     }
 
     class TestUpdateBookmarks extends Test {
         private int updateWithNullCol(long id, String colName) throws Exception {
             ContentValues u = new ContentValues();
             u.putNull(colName);
 
@@ -708,16 +748,17 @@ public class testBrowserProvider extends
             u.put(BrowserContract.Bookmarks.TYPE, BrowserContract.Bookmarks.TYPE_FOLDER);
             u.put(BrowserContract.Bookmarks.POSITION, 10);
 
             int updated = mProvider.update(BrowserContract.Bookmarks.CONTENT_URI, u,
                                            BrowserContract.Bookmarks._ID + " = ?",
                                            new String[] { String.valueOf(id) });
 
             mAsserter.is((updated == 1), true, "Inserted bookmark was updated");
+            c.close();
 
             c = getBookmarkById(id);
             mAsserter.is(c.moveToFirst(), true, "Updated bookmark found");
 
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.Bookmarks.TITLE)), u.getAsString(BrowserContract.Bookmarks.TITLE),
                          "Inserted bookmark has correct title");
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.Bookmarks.URL)), u.getAsString(BrowserContract.Bookmarks.URL),
                          "Inserted bookmark has correct URL");
@@ -747,22 +788,24 @@ public class testBrowserProvider extends
             updated = updateWithNullCol(id, BrowserContract.Bookmarks.TYPE);
             mAsserter.is((updated > 0), false,
                          "Should not be able to update bookmark with null type");
 
             u = new ContentValues();
             u.put(BrowserContract.Bookmarks.URL, "http://examples2.com");
 
             updated = mProvider.update(ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id), u, null, null);
+            c.close();
 
             c = getBookmarkById(id);
             mAsserter.is(c.moveToFirst(), true, "Updated bookmark found");
 
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.Bookmarks.URL)), u.getAsString(BrowserContract.Bookmarks.URL),
                          "Updated bookmark has correct URL using URI with id");
+            c.close();
         }
     }
 
     class TestUpdateBookmarksFavicons extends Test {
         @Override
         public void test() throws Exception {
             ContentValues b = createOneBookmark();
 
@@ -779,22 +822,24 @@ public class testBrowserProvider extends
             Cursor c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Inserted favicon found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"),
                          favicon, "Inserted favicon has corresponding favicon image");
 
             ContentValues u = createFaviconEntry(pageUrl, newFavicon);
             mProvider.update(BrowserContract.Favicons.CONTENT_URI, u, null, null);
+            c.close();
 
             c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Updated favicon found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"),
                          newFavicon, "Updated favicon has corresponding favicon image");
+            c.close();
         }
     }
 
     /**
      * Create a folder of one thousand and one bookmarks, then impose an order
      * on them.
      *
      * Verify that the reordering worked by querying.
@@ -820,16 +865,17 @@ public class testBrowserProvider extends
                     mAsserter.is(pos, (long) i, "Position matches sequence.");
                     mAsserter.is(guid, items[i], "GUID matches sequence.");
                 }
                 ++i;
                 c.moveToNext();
             }
 
             mAsserter.is(i, count, "Folder has the right number of children.");
+            c.close();
         }
 
         public static final int NUMBER_OF_CHILDREN = 1001;
         @Override
         public void test() throws Exception {
             // Create the containing folder.
             ContentValues folder = createBookmark("FolderFolder", "", mMobileFolderId,
                                                   BrowserContract.Bookmarks.TYPE_FOLDER, 0, "",
@@ -916,16 +962,17 @@ public class testBrowserProvider extends
 
             id = insertWithNullCol(BrowserContract.History.URL);
             mAsserter.is(new Long(id), new Long(-1),
                          "Should not be able to insert history with null URL");
 
             id = insertWithNullCol(BrowserContract.History.VISITS);
             mAsserter.is(new Long(id), new Long(-1),
                          "Should not be able to insert history with null number of visits");
+            c.close();
         }
     }
 
     class TestInsertHistoryFavicons extends Test {
         @Override
         public void test() throws Exception {
             ContentValues h = createOneHistoryEntry();
 
@@ -938,32 +985,35 @@ public class testBrowserProvider extends
             mProvider.insert(BrowserContract.Favicons.CONTENT_URI, createFaviconEntry(pageUrl, favicon));
 
             Cursor c = getHistoryEntryById(id, new String[] { BrowserContract.History.FAVICON });
 
             mAsserter.is(c.moveToFirst(), true, "Inserted history entry found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.History.FAVICON)), "UTF8"),
                          favicon, "Inserted history entry has corresponding favicon image");
+            c.close();
 
             c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Inserted favicon found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"),
                          favicon, "Inserted favicon has corresponding favicon image");
+            c.close();
         }
     }
 
     class TestDeleteHistory extends Test {
         private long insertOneHistoryEntry() throws Exception {
             ContentValues h = createOneHistoryEntry();
             long id = ContentUris.parseId(mProvider.insert(BrowserContract.History.CONTENT_URI, h));
 
             Cursor c = getHistoryEntryById(id);
             mAsserter.is(c.moveToFirst(), true, "Inserted history entry found");
+            c.close();
 
             return id;
         }
 
         @Override
         public void test() throws Exception {
             long id = insertOneHistoryEntry();
 
@@ -976,29 +1026,32 @@ public class testBrowserProvider extends
             Cursor c = getHistoryEntryById(appendUriParam(BrowserContract.History.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), id);
             mAsserter.is(c.moveToFirst(), true, "Deleted history entry was only marked as deleted");
 
             deleted = mProvider.delete(appendUriParam(BrowserContract.History.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"),
                                        BrowserContract.History._ID + " = ?",
                                        new String[] { String.valueOf(id) });
 
             mAsserter.is((deleted == 1), true, "Inserted history entry was deleted");
+            c.close();
 
             c = getHistoryEntryById(appendUriParam(BrowserContract.History.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1"), id);
             mAsserter.is(c.moveToFirst(), false, "Inserted history is now actually deleted");
 
             id = insertOneHistoryEntry();
 
             deleted = mProvider.delete(ContentUris.withAppendedId(BrowserContract.History.CONTENT_URI, id), null, null);
             mAsserter.is((deleted == 1), true,
                          "Inserted history entry was deleted using URI with id");
+            c.close();
 
             c = getHistoryEntryById(id);
             mAsserter.is(c.moveToFirst(), false,
                          "Inserted history entry can't be found after deletion using URI with ID");
+            c.close();
         }
     }
 
     class TestDeleteHistoryFavicons extends Test {
         @Override
         public void test() throws Exception {
             ContentValues h = createOneHistoryEntry();
 
@@ -1007,19 +1060,21 @@ public class testBrowserProvider extends
 
             // Insert the favicon into the favicons table
             mProvider.insert(BrowserContract.Favicons.CONTENT_URI, createFaviconEntry(pageUrl, "FAVICON"));
 
             Cursor c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Inserted favicon found");
 
             mProvider.delete(ContentUris.withAppendedId(BrowserContract.History.CONTENT_URI, id), null, null);
+            c.close();
 
             c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), false, "Favicon is deleted with last reference to it");
+            c.close();
         }
     }
 
     class TestUpdateHistory extends Test {
         private int updateWithNullCol(long id, String colName) throws Exception {
             ContentValues u = new ContentValues();
             u.putNull(colName);
 
@@ -1051,16 +1106,17 @@ public class testBrowserProvider extends
             u.put(BrowserContract.History.TITLE, h.getAsString(BrowserContract.History.TITLE) + "CHANGED");
             u.put(BrowserContract.History.URL, h.getAsString(BrowserContract.History.URL) + "/more/stuff");
 
             int updated = mProvider.update(BrowserContract.History.CONTENT_URI, u,
                                            BrowserContract.History._ID + " = ?",
                                            new String[] { String.valueOf(id) });
 
             mAsserter.is((updated == 1), true, "Inserted history entry was updated");
+            c.close();
 
             c = getHistoryEntryById(id);
             mAsserter.is(c.moveToFirst(), true, "Updated history entry found");
 
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.History.TITLE)), u.getAsString(BrowserContract.History.TITLE),
                          "Updated history entry has correct title");
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.History.URL)), u.getAsString(BrowserContract.History.URL),
                          "Updated history entry has correct URL");
@@ -1084,22 +1140,24 @@ public class testBrowserProvider extends
             updated = updateWithNullCol(id, BrowserContract.History.VISITS);
             mAsserter.is((updated > 0), false,
                          "Should not be able to update history with null number of visits");
 
             u = new ContentValues();
             u.put(BrowserContract.History.URL, "http://examples2.com");
 
             updated = mProvider.update(ContentUris.withAppendedId(BrowserContract.History.CONTENT_URI, id), u, null, null);
+            c.close();
 
             c = getHistoryEntryById(id);
             mAsserter.is(c.moveToFirst(), true, "Updated history entry found");
 
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.History.URL)), u.getAsString(BrowserContract.History.URL),
                          "Updated history entry has correct URL using URI with id");
+            c.close();
         }
     }
 
     class TestUpdateHistoryFavicons extends Test {
         @Override
         public void test() throws Exception {
             ContentValues h = createOneHistoryEntry();
 
@@ -1116,22 +1174,24 @@ public class testBrowserProvider extends
             mAsserter.is(c.moveToFirst(), true, "Inserted favicon found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"),
                          favicon, "Inserted favicon has corresponding favicon image");
 
             ContentValues u = createFaviconEntry(pageUrl, newFavicon);
 
             mProvider.update(BrowserContract.Favicons.CONTENT_URI, u, null, null);
+            c.close();
 
             c = getFaviconsByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Updated favicon found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Combined.FAVICON)), "UTF8"),
                          newFavicon, "Updated favicon has corresponding favicon image");
+            c.close();
         }
     }
 
     class TestUpdateOrInsertHistory extends Test {
         private final String TEST_URL_1 = "http://example.com";
         private final String TEST_URL_2 = "http://example.org";
         private final String TEST_TITLE = "Example";
 
@@ -1190,16 +1250,17 @@ public class testBrowserProvider extends
             values = new ContentValues();
             values.put(BrowserContract.History.DATE_LAST_VISITED, System.currentTimeMillis());
             values.put(BrowserContract.History.TITLE, TEST_TITLE);
 
             updated = mProvider.update(updateOrInsertHistoryUri, values,
                                        BrowserContract.History._ID + " = ?",
                                        new String[] { String.valueOf(id) });
             mAsserter.is((updated == 1), true, "Inserted history entry was updated");
+            c.close();
 
             c = getHistoryEntryById(id);
             mAsserter.is(c.moveToFirst(), true, "Updated history entry found");
 
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.History.TITLE)), TEST_TITLE,
                          "Updated history entry has correct title");
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.History.VISITS))), new Long(2),
                          "Updated history entry has correct number of visits");
@@ -1215,16 +1276,18 @@ public class testBrowserProvider extends
             values.put(BrowserContract.History.VISITS, 10);
 
             updated = mProvider.update(updateOrInsertHistoryUri, values,
                                            BrowserContract.History.URL + " = ?",
                                            new String[] { values.getAsString(BrowserContract.History.URL) });
             mAsserter.is((updated == 1), true, "History entry was inserted");
 
             id = getHistoryEntryIdByUrl(TEST_URL_2);
+            c.close();
+
             c = getHistoryEntryById(id);
             mAsserter.is(c.moveToFirst(), true, "History entry was inserted");
 
             dateCreated = c.getLong(c.getColumnIndex(BrowserContract.History.DATE_CREATED));
             dateModified = c.getLong(c.getColumnIndex(BrowserContract.History.DATE_MODIFIED));
 
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.History.VISITS))), new Long(10),
                          "Inserted history entry has correct specified number of visits");
@@ -1234,30 +1297,32 @@ public class testBrowserProvider extends
             // Update the history entry, specifying additional visit count
             values = new ContentValues();
             values.put(BrowserContract.History.VISITS, 10);
 
             updated = mProvider.update(updateOrInsertHistoryUri, values,
                                        BrowserContract.History._ID + " = ?",
                                        new String[] { String.valueOf(id) });
             mAsserter.is((updated == 1), true, "Inserted history entry was updated");
+            c.close();
 
             c = getHistoryEntryById(id);
             mAsserter.is(c.moveToFirst(), true, "Updated history entry found");
 
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.History.TITLE)), TEST_TITLE,
                          "Updated history entry has correct unchanged title");
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.History.URL)), TEST_URL_2,
                          "Updated history entry has correct unchanged URL");
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.History.VISITS))), new Long(20),
                          "Updated history entry has correct number of visits");
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.History.DATE_CREATED))), new Long(dateCreated),
                          "Updated history entry has same creation date");
             mAsserter.isnot(new Long(c.getLong(c.getColumnIndex(BrowserContract.History.DATE_MODIFIED))), new Long(dateModified),
                             "Updated history entry has new modification date");
+            c.close();
 
         }
     }
 
     class TestInsertHistoryThumbnails extends Test {
         @Override
         public void test() throws Exception {
             ContentValues h = createOneHistoryEntry();
@@ -1270,16 +1335,17 @@ public class testBrowserProvider extends
             // Insert the thumbnail into the thumbnails table
             mProvider.insert(BrowserContract.Thumbnails.CONTENT_URI, createThumbnailEntry(pageUrl, thumbnail));
 
             Cursor c = getThumbnailByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Inserted thumbnail found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Thumbnails.DATA)), "UTF8"),
                          thumbnail, "Inserted thumbnail has corresponding thumbnail image");
+            c.close();
         }
     }
 
     class TestUpdateHistoryThumbnails extends Test {
         @Override
         public void test() throws Exception {
             ContentValues h = createOneHistoryEntry();
 
@@ -1296,22 +1362,24 @@ public class testBrowserProvider extends
             mAsserter.is(c.moveToFirst(), true, "Inserted thumbnail found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Thumbnails.DATA)), "UTF8"),
                          thumbnail, "Inserted thumbnail has corresponding thumbnail image");
 
             ContentValues u = createThumbnailEntry(pageUrl, newThumbnail);
 
             mProvider.update(BrowserContract.Thumbnails.CONTENT_URI, u, null, null);
+            c.close();
 
             c = getThumbnailByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Updated thumbnail found");
 
             mAsserter.is(new String(c.getBlob(c.getColumnIndex(BrowserContract.Thumbnails.DATA)), "UTF8"),
                          newThumbnail, "Updated thumbnail has corresponding thumbnail image");
+            c.close();
         }
     }
 
     class TestDeleteHistoryThumbnails extends Test {
         @Override
         public void test() throws Exception {
             ContentValues h = createOneHistoryEntry();
 
@@ -1320,19 +1388,21 @@ public class testBrowserProvider extends
 
             // Insert the thumbnail into the thumbnails table
             mProvider.insert(BrowserContract.Thumbnails.CONTENT_URI, createThumbnailEntry(pageUrl, "THUMBNAIL"));
 
             Cursor c = getThumbnailByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), true, "Inserted thumbnail found");
 
             mProvider.delete(ContentUris.withAppendedId(BrowserContract.History.CONTENT_URI, id), null, null);
+            c.close();
 
             c = getThumbnailByUrl(pageUrl);
             mAsserter.is(c.moveToFirst(), false, "Thumbnail is deleted with last reference to it");
+            c.close();
         }
     }
 
     class TestCombinedView extends Test {
         @Override
         public void test() throws Exception {
             final String TITLE_1 = "Test Page 1";
             final String TITLE_2 = "Test Page 2";
@@ -1430,16 +1500,17 @@ public class testBrowserProvider extends
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.HISTORY_ID))), new Long(combinedHistoryId),
                          "Combined entry has correct history id");
             mAsserter.is(c.getString(c.getColumnIndex(BrowserContract.Combined.URL)), URL_3,
                          "Combined entry has correct url");
             mAsserter.is(c.getInt(c.getColumnIndex(BrowserContract.Combined.VISITS)), VISITS,
                          "Combined entry has correct number of visits");
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.DATE_LAST_VISITED))), new Long(LAST_VISITED),
                          "Combined entry has correct last visit time");
+            c.close();
         }
     }
 
     class TestCombinedViewDisplay extends Test {
         @Override
         public void test() throws Exception {
             final String TITLE_1 = "Test Page 1";
             final String TITLE_2 = "Test Page 2";
@@ -1490,16 +1561,17 @@ public class testBrowserProvider extends
                 long id = c.getLong(c.getColumnIndex(BrowserContract.Combined.BOOKMARK_ID));
 
                 int display = c.getInt(c.getColumnIndex(BrowserContract.Combined.DISPLAY));
                 int expectedDisplay = (id == readingListItemId || id == readingListItemId2 ? BrowserContract.Combined.DISPLAY_READER : BrowserContract.Combined.DISPLAY_NORMAL);
 
                 mAsserter.is(new Integer(display), new Integer(expectedDisplay),
                                  "Combined display column should always be DISPLAY_READER for the reading list item");
             }
+            c.close();
         }
     }
 
     class TestCombinedViewWithDeletedBookmark extends Test {
         @Override
         public void test() throws Exception {
             final String TITLE = "Test Page 1";
             final String URL = "http://example.com";
@@ -1522,23 +1594,25 @@ public class testBrowserProvider extends
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.BOOKMARK_ID))), new Long(combinedBookmarkId),
                          "Bookmark id should be set correctly on combined entry");
 
             int deleted = mProvider.delete(BrowserContract.Bookmarks.CONTENT_URI,
                                            BrowserContract.Bookmarks._ID + " = ?",
                                            new String[] { String.valueOf(combinedBookmarkId) });
 
             mAsserter.is((deleted == 1), true, "Inserted combined bookmark was deleted");
+            c.close();
 
             c = mProvider.query(BrowserContract.Combined.CONTENT_URI, null, "", null, null);
             mAsserter.is(c.getCount(), 1, "1 combined entry found");
 
             mAsserter.is(c.moveToFirst(), true, "Found combined entry without bookmark id");
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.BOOKMARK_ID))), new Long(0),
                          "Bookmark id should not be set to removed bookmark id");
+            c.close();
         }
     }
 
     class TestCombinedViewWithDeletedReadingListItem extends Test {
         @Override
         public void test() throws Exception {
             final String TITLE = "Test Page 1";
             final String URL = "http://example.com";
@@ -1564,25 +1638,27 @@ public class testBrowserProvider extends
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.DISPLAY))), new Long(BrowserContract.Combined.DISPLAY_READER),
                          "Combined entry should have reader display type");
 
             int deleted = mProvider.delete(BrowserContract.Bookmarks.CONTENT_URI,
                                            BrowserContract.Bookmarks._ID + " = ?",
                                            new String[] { String.valueOf(combinedReadingListItemId) });
 
             mAsserter.is((deleted == 1), true, "Inserted combined reading list item was deleted");
+            c.close();
 
             c = mProvider.query(BrowserContract.Combined.CONTENT_URI, null, "", null, null);
             mAsserter.is(c.getCount(), 1, "1 combined entry found");
 
             mAsserter.is(c.moveToFirst(), true, "Found combined entry without bookmark id");
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.BOOKMARK_ID))), new Long(0),
                          "Bookmark id should not be set to removed bookmark id");
             mAsserter.is(new Long(c.getLong(c.getColumnIndex(BrowserContract.Combined.DISPLAY))), new Long(BrowserContract.Combined.DISPLAY_NORMAL),
                          "Combined entry should have reader display type");
+            c.close();
         }
     }
 
     class TestExpireHistory extends Test {
         private void createFakeHistory(long timeShift, int count) {
             // Insert a bunch of very new entries
             ContentValues[] allVals = new ContentValues[count];
             long time = System.currentTimeMillis() - timeShift;
@@ -1603,96 +1679,101 @@ public class testBrowserProvider extends
                 ContentValues cv = new ContentValues();
                 cv.put(BrowserContract.History.DATE_CREATED, time);
                 cv.put(BrowserContract.History.DATE_MODIFIED, time);
                 mProvider.update(BrowserContract.History.CONTENT_URI, cv, BrowserContract.History.URL + " = ?",
                                  new String[] { "http://www.test.org/" + i });
             }
 
             Cursor c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null);
-            mAsserter.is(c.getCount(), count, count + " history entries found");
+
+            assertCountIsAndClose(c, count, count + " history entries found");
 
             // add thumbnails for each entry
             allVals = new ContentValues[count];
             for (int i = 0; i < count; i++) {
                 allVals[i] = new ContentValues();
                 allVals[i].put(BrowserContract.Thumbnails.DATA, i);
                 allVals[i].put(BrowserContract.Thumbnails.URL, "http://www.test.org/" + i);
             }
 
             inserts = mProvider.bulkInsert(BrowserContract.Thumbnails.CONTENT_URI, allVals);
             mAsserter.is(inserts, count, "Expected number of inserts matches");
 
             c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null);
-            mAsserter.is(c.getCount(), count, count + " thumbnails entries found");
+            assertCountIsAndClose(c, count, count + " thumbnails entries found");
         }
 
         @Override
         public void test() throws Exception {
             final int count = 3000;
             final int thumbCount = 15;
 
             // insert a bunch of new entries
             createFakeHistory(0, count);
 
             // expiring with a normal priority should not delete new entries
             Uri url = appendUriParam(BrowserContract.History.CONTENT_OLD_URI, BrowserContract.PARAM_EXPIRE_PRIORITY, "NORMAL");
             mProvider.delete(url, null, null);
             Cursor c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null);
-            mAsserter.is(c.getCount(), count, count + " history entries found");
+            assertCountIsAndClose(c, count, count + " history entries found");
 
             // expiring with a normal priority should delete all but 10 thumbnails
             c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null);
-            mAsserter.is(c.getCount(), thumbCount, thumbCount + " thumbnails found");
+            assertCountIsAndClose(c, thumbCount, thumbCount + " thumbnails found");
 
             ensureEmptyDatabase();
-            // insert a bunch of new entries
+
+            // Insert a bunch of new entries.
             createFakeHistory(0, count);
 
-            // expiring with a aggressive priority should leave 500 entries
+            // Expiring with a aggressive priority should leave 500 entries.
             url = appendUriParam(BrowserContract.History.CONTENT_OLD_URI, BrowserContract.PARAM_EXPIRE_PRIORITY, "AGGRESSIVE");
             mProvider.delete(url, null, null);
+
             c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null);
-            mAsserter.is(c.getCount(), 500, "500 history entries found");
+            assertCountIsAndClose(c, 500, "500 history entries found");
 
-            // expiring with a aggressive priority should delete all but 10 thumbnails
+            // Expiring with a aggressive priority should delete all but 10 thumbnails.
             c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null);
-            mAsserter.is(c.getCount(), thumbCount, thumbCount + " thumbnails found");
+            assertCountIsAndClose(c, thumbCount, thumbCount + " thumbnails found");
 
             ensureEmptyDatabase();
-            // insert a bunch of entries with an old time created/modified
+
+            // Insert a bunch of entries with an old time created/modified.
             long time = 1000L * 60L * 60L * 24L * 30L * 3L;
             createFakeHistory(time, count);
 
-            // expiring with an normal priority should remove at most 1000 entries
-            // entries leaving at least 2000
+            // Expiring with an normal priority should remove at most 1000 entries,
+            // entries leaving at least 2000.
             url = appendUriParam(BrowserContract.History.CONTENT_OLD_URI, BrowserContract.PARAM_EXPIRE_PRIORITY, "NORMAL");
             mProvider.delete(url, null, null);
+
             c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null);
-            mAsserter.is(c.getCount(), 2000, "2000 history entries found");
+            assertCountIsAndClose(c, 2000, "2000 history entries found");
 
-            // expiring with a normal priority should delete all but 10 thumbnails
+            // Expiring with a normal priority should delete all but 10 thumbnails.
             c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null);
-            mAsserter.is(c.getCount(), thumbCount, thumbCount + " thumbnails found");
+            assertCountIsAndClose(c, thumbCount, thumbCount + " thumbnails found");
 
             ensureEmptyDatabase();
             // insert a bunch of entries with an old time created/modified
             time = 1000L * 60L * 60L * 24L * 30L * 3L;
             createFakeHistory(time, count);
 
-            // expiring with an agressive priority should remove old
-            // entries leaving at least 500
+            // Expiring with an aggressive priority should remove old
+            // entries, leaving at least 500.
             url = appendUriParam(BrowserContract.History.CONTENT_OLD_URI, BrowserContract.PARAM_EXPIRE_PRIORITY, "AGGRESSIVE");
             mProvider.delete(url, null, null);
             c = mProvider.query(BrowserContract.History.CONTENT_URI, null, "", null, null);
-            mAsserter.is(c.getCount(), 500, "500 history entries found");
+            assertCountIsAndClose(c, 500, "500 history entries found");
 
             // expiring with an aggressive priority should delete all but 10 thumbnails
             c = mProvider.query(BrowserContract.Thumbnails.CONTENT_URI, null, null, null, null);
-            mAsserter.is(c.getCount(), thumbCount, thumbCount + " thumbnails found");
+            assertCountIsAndClose(c, thumbCount, thumbCount + " thumbnails found");
         }
     }
 
     /*
      * Verify that insert, update, delete, and bulkInsert operations
      * notify the ambient content resolver.  Each operation calls the
      * content resolver notifyChange method synchronously, so it is
      * okay to test sequentially.
@@ -1771,9 +1852,21 @@ public class testBrowserProvider extends
 
             mAsserter.is(Long.valueOf(numBulkInserted),
                          Long.valueOf(1),
                          "Correct number of items are bulkInserted");
 
             ensureOnlyChangeNotifiedStartsWith(BrowserContract.History.CONTENT_URI, "bulkInsert");
         }
     }
+
+    /**
+     * Assert that the provided cursor has the expected number of rows,
+     * closing the cursor afterwards.
+     */
+    private void assertCountIsAndClose(Cursor c, int expectedCount, String message) {
+        try {
+            mAsserter.is(c.getCount(), expectedCount, message);
+        } finally {
+            c.close();
+        }
+    }
 }