Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 package org.mozilla.gecko.tests;
7 import java.util.HashSet;
8 import java.util.Random;
9 import java.util.concurrent.Callable;
11 import org.mozilla.gecko.db.BrowserContract;
12 import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
13 import org.mozilla.gecko.db.ReadingListProvider;
15 import android.content.ContentProvider;
16 import android.content.ContentUris;
17 import android.content.ContentValues;
18 import android.database.Cursor;
19 import android.database.sqlite.SQLiteDatabase;
20 import android.net.Uri;
22 public class testReadingListProvider extends ContentProviderTest {
24 private static final String DB_NAME = "browser.db";
26 // List of tests to be run sorted by dependency.
27 private final TestCase[] TESTS_TO_RUN = { new TestInsertItems(),
28 new TestDeleteItems(),
29 new TestUpdateItems(),
30 new TestBatchOperations(),
31 new TestBrowserProviderNotifications() };
33 // Columns used to test for item equivalence.
34 final String[] TEST_COLUMNS = { ReadingListItems.TITLE,
35 ReadingListItems.URL,
36 ReadingListItems.EXCERPT,
37 ReadingListItems.LENGTH,
38 ReadingListItems.DATE_CREATED };
40 // Indicates that insertions have been tested. ContentProvider.insert
41 // has been proven to work.
42 private boolean mContentProviderInsertTested = false;
44 // Indicates that updates have been tested. ContentProvider.update
45 // has been proven to work.
46 private boolean mContentProviderUpdateTested = false;
48 /**
49 * Factory function that makes new ReadingListProvider instances.
50 * <p>
51 * We want a fresh provider each test, so this should be invoked in
52 * <code>setUp</code> before each individual test.
53 */
54 private static Callable<ContentProvider> sProviderFactory = new Callable<ContentProvider>() {
55 @Override
56 public ContentProvider call() {
57 return new ReadingListProvider();
58 }
59 };
61 @Override
62 public void setUp() throws Exception {
63 super.setUp(sProviderFactory, BrowserContract.READING_LIST_AUTHORITY, DB_NAME);
64 for (TestCase test: TESTS_TO_RUN) {
65 mTests.add(test);
66 }
67 }
69 public void testReadingListProviderTests() throws Exception {
70 for (Runnable test : mTests) {
71 setTestName(test.getClass().getSimpleName());
72 ensureEmptyDatabase();
73 test.run();
74 }
76 // Ensure browser initialization is complete before completing test,
77 // so that the minidumps directory is consistently created.
78 blockForGeckoReady();
79 }
81 /**
82 * Verify that we can insert a reading list item into the DB.
83 */
84 private class TestInsertItems extends TestCase {
85 @Override
86 public void test() throws Exception {
87 ContentValues b = createFillerReadingListItem();
88 long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, b));
89 Cursor c = getItemById(id);
91 try {
92 mAsserter.ok(c.moveToFirst(), "Inserted item found", "");
93 assertRowEqualsContentValues(c, b);
94 } finally {
95 c.close();
96 }
98 testInsertWithNullCol(ReadingListItems.GUID);
99 mContentProviderInsertTested = true;
100 }
102 /**
103 * Test that insertion fails when a required column
104 * is null.
105 */
106 private void testInsertWithNullCol(String colName) {
107 ContentValues b = createFillerReadingListItem();
108 b.putNull(colName);
110 try {
111 ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, b));
112 // If we get to here, the flawed insertion succeeded. Fail the test.
113 mAsserter.ok(false, "Insertion did not succeed with " + colName + " == null", "");
114 } catch (NullPointerException e) {
115 // Indicates test was successful.
116 }
117 }
118 }
120 /**
121 * Verify that we can remove a reading list item from the DB.
122 */
123 private class TestDeleteItems extends TestCase {
125 @Override
126 public void test() throws Exception {
127 long id = insertAnItemWithAssertion();
128 // Test that the item is only marked as deleted and
129 // not removed from the database.
130 testNonFirefoxSyncDelete(id);
132 // Test that the item is removed from the database.
133 testFirefoxSyncDelete(id);
135 id = insertAnItemWithAssertion();
136 // Test that deleting works with only a URI.
137 testDeleteWithItemURI(id);
138 }
140 /**
141 * Delete an item with PARAM_IS_SYNC unset and verify that item was only marked
142 * as deleted and not actually removed from the database. Also verify that the item
143 * marked as deleted doesn't show up in a query.
144 *
145 * @param id of the item to be deleted
146 */
147 private void testNonFirefoxSyncDelete(long id) {
148 final int deleted = mProvider.delete(ReadingListItems.CONTENT_URI,
149 ReadingListItems._ID + " = ?",
150 new String[] { String.valueOf(id) });
152 mAsserter.is(deleted, 1, "Inserted item was deleted");
154 // PARAM_SHOW_DELETED in the URI allows items marked as deleted to be
155 // included in the query.
156 Uri uri = appendUriParam(ReadingListItems.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1");
157 assertItemExistsByID(uri, id, "Deleted item was only marked as deleted");
159 // Test that the 'deleted' item does not show up in a query when PARAM_SHOW_DELETED
160 // is not specified in the URI.
161 assertItemDoesNotExistByID(id, "Inserted item can't be found after deletion");
162 }
164 /**
165 * Delete an item with PARAM_IS_SYNC=1 and verify that item
166 * was actually removed from the database.
167 *
168 * @param id of the item to be deleted
169 */
170 private void testFirefoxSyncDelete(long id) {
171 final int deleted = mProvider.delete(appendUriParam(ReadingListItems.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1"),
172 ReadingListItems._ID + " = ?",
173 new String[] { String.valueOf(id) });
175 mAsserter.is(deleted, 1, "Inserted item was deleted");
177 Uri uri = appendUriParam(ReadingListItems.CONTENT_URI, BrowserContract.PARAM_SHOW_DELETED, "1");
178 assertItemDoesNotExistByID(uri, id, "Inserted item is now actually deleted");
179 }
181 /**
182 * Delete an item with its URI and verify that the item
183 * was actually removed from the database.
184 *
185 * @param id of the item to be deleted
186 */
187 private void testDeleteWithItemURI(long id) {
188 final int deleted = mProvider.delete(ContentUris.withAppendedId(ReadingListItems.CONTENT_URI, id), null, null);
189 mAsserter.is(deleted, 1, "Inserted item was deleted using URI with id");
190 }
191 }
193 /**
194 * Verify that we can update reading list items.
195 */
196 private class TestUpdateItems extends TestCase {
198 @Override
199 public void test() throws Exception {
200 // We should be able to insert into the DB.
201 ensureCanInsert();
203 ContentValues original = createFillerReadingListItem();
204 long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, original));
205 int updated = 0;
206 Long originalDateCreated = null;
207 Long originalDateModified = null;
208 ContentValues updates = new ContentValues();
209 Cursor c = getItemById(id);
210 try {
211 mAsserter.ok(c.moveToFirst(), "Inserted item found", "");
213 originalDateCreated = c.getLong(c.getColumnIndex(ReadingListItems.DATE_CREATED));
214 originalDateModified = c.getLong(c.getColumnIndex(ReadingListItems.DATE_MODIFIED));
216 updates.put(ReadingListItems.TITLE, original.getAsString(ReadingListItems.TITLE) + "CHANGED");
217 updates.put(ReadingListItems.URL, original.getAsString(ReadingListItems.URL) + "/more/stuff");
218 updates.put(ReadingListItems.EXCERPT, original.getAsString(ReadingListItems.EXCERPT) + "CHANGED");
220 updated = mProvider.update(ReadingListItems.CONTENT_URI, updates,
221 ReadingListItems._ID + " = ?",
222 new String[] { String.valueOf(id) });
224 mAsserter.is(updated, 1, "Inserted item was updated");
225 } finally {
226 c.close();
227 }
229 // Name change for clarity. These values will be compared with the
230 // current cursor row.
231 ContentValues expectedValues = updates;
232 c = getItemById(id);
233 try {
234 mAsserter.ok(c.moveToFirst(), "Updated item found", "");
235 mAsserter.isnot(c.getLong(c.getColumnIndex(ReadingListItems.DATE_MODIFIED)),
236 originalDateModified,
237 "Date modified should have changed");
239 // DATE_CREATED and LENGTH should equal old values since they weren't updated.
240 expectedValues.put(ReadingListItems.DATE_CREATED, originalDateCreated);
241 expectedValues.put(ReadingListItems.LENGTH, original.getAsString(ReadingListItems.LENGTH));
242 assertRowEqualsContentValues(c, expectedValues, /* compareDateModified */ false);
243 } finally {
244 c.close();
245 }
247 // Test that updates on an item that doesn't exist does not modify any rows.
248 testUpdateWithInvalidID();
250 // Test that update fails when a GUID is null.
251 testUpdateWithNullCol(id, ReadingListItems.GUID);
253 mContentProviderUpdateTested = true;
254 }
256 /**
257 * Test that updates on an item that doesn't exist does
258 * not modify any rows.
259 *
260 * @param id of the item to be deleted
261 */
262 private void testUpdateWithInvalidID() {
263 ensureEmptyDatabase();
264 final ContentValues b = createFillerReadingListItem();
265 final long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, b));
266 final long INVALID_ID = id + 1;
267 final ContentValues updates = new ContentValues();
268 updates.put(ReadingListItems.TITLE, b.getAsString(ReadingListItems.TITLE) + "CHANGED");
269 final int updated = mProvider.update(ReadingListItems.CONTENT_URI, updates,
270 ReadingListItems._ID + " = ?",
271 new String[] { String.valueOf(INVALID_ID) });
272 mAsserter.is(updated, 0, "Should not be able to update item with an invalid GUID");
273 }
275 /**
276 * Test that update fails when a required column is null.
277 */
278 private int testUpdateWithNullCol(long id, String colName) {
279 ContentValues updates = new ContentValues();
280 updates.putNull(colName);
282 int updated = mProvider.update(ReadingListItems.CONTENT_URI, updates,
283 ReadingListItems._ID + " = ?",
284 new String[] { String.valueOf(id) });
286 mAsserter.is(updated, 0, "Should not be able to update item with " + colName + " == null ");
287 return updated;
288 }
289 }
291 private class TestBatchOperations extends TestCase {
292 private static final int ITEM_COUNT = 10;
294 /**
295 * Insert a bunch of items into the DB with the bulkInsert
296 * method and verify that they are there.
297 */
298 private void testBulkInsert() {
299 ensureEmptyDatabase();
300 final ContentValues allVals[] = new ContentValues[ITEM_COUNT];
301 final HashSet<String> urls = new HashSet<String>();
302 for (int i = 0; i < ITEM_COUNT; i++) {
303 final String url = "http://www.test.org/" + i;
304 allVals[i] = new ContentValues();
305 allVals[i].put(ReadingListItems.TITLE, "Test" + i);
306 allVals[i].put(ReadingListItems.URL, url);
307 allVals[i].put(ReadingListItems.EXCERPT, "EXCERPT" + i);
308 allVals[i].put(ReadingListItems.LENGTH, i);
309 urls.add(url);
310 }
312 int inserts = mProvider.bulkInsert(ReadingListItems.CONTENT_URI, allVals);
313 mAsserter.is(inserts, ITEM_COUNT, "Excepted number of inserts matches");
315 Cursor c = mProvider.query(ReadingListItems.CONTENT_URI, null,
316 null,
317 null,
318 null);
319 try {
320 while (c.moveToNext()) {
321 final String url = c.getString(c.getColumnIndex(ReadingListItems.URL));
322 mAsserter.ok(urls.contains(url), "Bulk inserted item with url == " + url + " was found in the DB", "");
323 // We should only be seeing each item once. Remove from set to prevent dups.
324 urls.remove(url);
325 }
326 } finally {
327 c.close();
328 }
329 }
331 @Override
332 public void test() {
333 testBulkInsert();
334 }
335 }
337 /*
338 * Verify that insert, update, delete, and bulkInsert operations
339 * notify the ambient content resolver. Each operation calls the
340 * content resolver notifyChange method synchronously, so it is
341 * okay to test sequentially.
342 */
343 private class TestBrowserProviderNotifications extends TestCase {
345 @Override
346 public void test() {
347 // We should be able to insert into the DB.
348 ensureCanInsert();
349 // We should be able to update the DB.
350 ensureCanUpdate();
352 final String CONTENT_URI = ReadingListItems.CONTENT_URI.toString();
354 mResolver.notifyChangeList.clear();
356 // Insert
357 final ContentValues h = createFillerReadingListItem();
358 long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, h));
360 mAsserter.isnot(id,
361 -1L,
362 "Inserted item has valid id");
364 ensureOnlyChangeNotifiedStartsWith(CONTENT_URI, "insert");
366 // Update
367 mResolver.notifyChangeList.clear();
368 h.put(ReadingListItems.TITLE, "http://newexample.com");
370 long numUpdated = mProvider.update(ReadingListItems.CONTENT_URI, h,
371 ReadingListItems._ID + " = ?",
372 new String[] { String.valueOf(id) });
374 mAsserter.is(numUpdated,
375 1L,
376 "Correct number of items are updated");
378 ensureOnlyChangeNotifiedStartsWith(CONTENT_URI, "update");
380 // Delete
381 mResolver.notifyChangeList.clear();
382 long numDeleted = mProvider.delete(ReadingListItems.CONTENT_URI, null, null);
384 mAsserter.is(numDeleted,
385 1L,
386 "Correct number of items are deleted");
388 ensureOnlyChangeNotifiedStartsWith(CONTENT_URI, "delete");
390 // Bulk insert
391 mResolver.notifyChangeList.clear();
392 final ContentValues[] hs = { createFillerReadingListItem(),
393 createFillerReadingListItem(),
394 createFillerReadingListItem() };
396 long numBulkInserted = mProvider.bulkInsert(ReadingListItems.CONTENT_URI, hs);
398 mAsserter.is(numBulkInserted,
399 3L,
400 "Correct number of items are bulkInserted");
402 ensureOnlyChangeNotifiedStartsWith(CONTENT_URI, "bulkInsert");
403 }
405 protected void ensureOnlyChangeNotifiedStartsWith(String expectedUri, String operation) {
406 mAsserter.is(Long.valueOf(mResolver.notifyChangeList.size()),
407 1L,
408 "Content observer was notified exactly once by " + operation);
410 final Uri uri = mResolver.notifyChangeList.poll();
412 mAsserter.isnot(uri,
413 null,
414 "Notification from " + operation + " was valid");
416 mAsserter.ok(uri.toString().startsWith(expectedUri),
417 "Content observer was notified exactly once by " + operation,
418 "");
419 }
420 }
422 /**
423 * Removes all items from the DB.
424 */
425 private void ensureEmptyDatabase() {
426 Uri uri = appendUriParam(ReadingListItems.CONTENT_URI, BrowserContract.PARAM_IS_SYNC, "1");
427 getWritableDatabase(uri).delete(ReadingListItems.TABLE_NAME, null, null);
428 }
431 private SQLiteDatabase getWritableDatabase(Uri uri) {
432 Uri testUri = appendUriParam(uri, BrowserContract.PARAM_IS_TEST, "1");
433 DelegatingTestContentProvider delegateProvider = (DelegatingTestContentProvider) mProvider;
434 ReadingListProvider readingListProvider = (ReadingListProvider) delegateProvider.getTargetProvider();
435 return readingListProvider.getWritableDatabaseForTesting(testUri);
436 }
438 /**
439 * Checks that the values in the cursor's current row match those
440 * in the ContentValues object.
441 *
442 * @param cursor over the row to be checked
443 * @param values to be checked
444 */
445 private void assertRowEqualsContentValues(Cursor cursorWithActual, ContentValues expectedValues, boolean compareDateModified) {
446 for (String column: TEST_COLUMNS) {
447 String expected = expectedValues.getAsString(column);
448 String actual = cursorWithActual.getString(cursorWithActual.getColumnIndex(column));
449 mAsserter.is(actual, expected, "Item has correct " + column);
450 }
452 if (compareDateModified) {
453 String expected = expectedValues.getAsString(ReadingListItems.DATE_MODIFIED);
454 String actual = cursorWithActual.getString(cursorWithActual.getColumnIndex(ReadingListItems.DATE_MODIFIED));
455 mAsserter.is(actual, expected, "Item has correct " + ReadingListItems.DATE_MODIFIED);
456 }
457 }
459 private void assertRowEqualsContentValues(Cursor cursorWithActual, ContentValues expectedValues) {
460 assertRowEqualsContentValues(cursorWithActual, expectedValues, true);
461 }
463 private ContentValues fillContentValues(String title, String url, String excerpt) {
464 ContentValues values = new ContentValues();
466 values.put(ReadingListItems.TITLE, title);
467 values.put(ReadingListItems.URL, url);
468 values.put(ReadingListItems.EXCERPT, excerpt);
469 values.put(ReadingListItems.LENGTH, excerpt.length());
471 return values;
472 }
474 private ContentValues createFillerReadingListItem() {
475 Random rand = new Random();
476 return fillContentValues("Example", "http://example.com/?num=" + rand.nextInt(), "foo bar");
477 }
479 private Cursor getItemById(Uri uri, long id, String[] projection) {
480 return mProvider.query(uri, projection,
481 ReadingListItems._ID + " = ?",
482 new String[] { String.valueOf(id) },
483 null);
484 }
486 private Cursor getItemById(long id) {
487 return getItemById(ReadingListItems.CONTENT_URI, id, null);
488 }
490 private Cursor getItemById(Uri uri, long id) {
491 return getItemById(uri, id, null);
492 }
494 /**
495 * Verifies that ContentProvider insertions have been tested.
496 */
497 private void ensureCanInsert() {
498 if (!mContentProviderInsertTested) {
499 mAsserter.ok(false, "ContentProvider insertions have not been tested yet.", "");
500 }
501 }
503 /**
504 * Verifies that ContentProvider updates have been tested.
505 */
506 private void ensureCanUpdate() {
507 if (!mContentProviderUpdateTested) {
508 mAsserter.ok(false, "ContentProvider updates have not been tested yet.", "");
509 }
510 }
512 private long insertAnItemWithAssertion() {
513 // We should be able to insert into the DB.
514 ensureCanInsert();
516 ContentValues v = createFillerReadingListItem();
517 long id = ContentUris.parseId(mProvider.insert(ReadingListItems.CONTENT_URI, v));
519 assertItemExistsByID(id, "Inserted item found");
520 return id;
521 }
523 private void assertItemExistsByID(Uri uri, long id, String msg) {
524 Cursor c = getItemById(uri, id);
525 try {
526 mAsserter.ok(c.moveToFirst(), msg, "");
527 } finally {
528 c.close();
529 }
530 }
532 private void assertItemExistsByID(long id, String msg) {
533 Cursor c = getItemById(id);
534 try {
535 mAsserter.ok(c.moveToFirst(), msg, "");
536 } finally {
537 c.close();
538 }
539 }
541 private void assertItemDoesNotExistByID(long id, String msg) {
542 Cursor c = getItemById(id);
543 try {
544 mAsserter.ok(!c.moveToFirst(), msg, "");
545 } finally {
546 c.close();
547 }
548 }
550 private void assertItemDoesNotExistByID(Uri uri, long id, String msg) {
551 Cursor c = getItemById(uri, id);
552 try {
553 mAsserter.ok(!c.moveToFirst(), msg, "");
554 } finally {
555 c.close();
556 }
557 }
558 }