diff -r 000000000000 -r 6474c204b198 mobile/android/base/tests/ContentProviderTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mobile/android/base/tests/ContentProviderTest.java Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,255 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko.tests; + +import java.io.File; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.concurrent.Callable; + +import org.mozilla.gecko.db.BrowserContract; +import org.mozilla.gecko.db.BrowserProvider; + +import android.content.ContentProvider; +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentValues; +import android.content.Context; +import android.content.OperationApplicationException; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.test.IsolatedContext; +import android.test.RenamingDelegatingContext; +import android.test.mock.MockContentResolver; +import android.test.mock.MockContext; + +/* + * ContentProviderTest provides the infrastructure to run content provider + * tests in an controlled/isolated environment, guaranteeing that your tests + * will not affect or be affected by any UI-related code. This is basically + * a heavily adapted port of Android's ProviderTestCase2 to work on Mozilla's + * infrastructure. + * + * For some tests, we need to have access to UI parts, or at least launch + * the activity so the assets with test data become available, which requires + * that we derive this test from BaseTest and consequently pull in some more + * UI code than we'd ideally want. Furthermore, we need to pass the + * Activity and not the instrumentation Context for the UI part to find some + * of its required resources. + * Similarly, we need to pass the Activity instead of the Instrumentation + * Context down to some of our users (still wrapped in the Delegating Provider) + * because they will stop working correctly if we do not. A typical problem + * is that databases used in the ContentProvider will be attempted to be + * opened twice. + */ +abstract class ContentProviderTest extends BaseTest { + protected ContentProvider mProvider; + protected ChangeRecordingMockContentResolver mResolver; + protected ArrayList mTests; + protected String mDatabaseName; + protected String mProviderAuthority; + protected IsolatedContext mProviderContext; + + private class ContentProviderMockContext extends MockContext { + @Override + public Resources getResources() { + // We will fail to find some resources if we don't point + // at the original activity. + return ((Context)getActivity()).getResources(); + } + + @Override + public String getPackageName() { + return getInstrumentation().getContext().getPackageName(); + } + + @Override + public String getPackageResourcePath() { + return getInstrumentation().getContext().getPackageResourcePath(); + } + + @Override + public File getDir(String name, int mode) { + return getInstrumentation().getContext().getDir(this.getClass().getSimpleName() + "_" + name, mode); + } + + @Override + public Context getApplicationContext() { + return this; + } + + @Override + public SharedPreferences getSharedPreferences(String name, int mode) { + return getInstrumentation().getContext().getSharedPreferences(name, mode); + } + + @Override + public ApplicationInfo getApplicationInfo() { + return getInstrumentation().getContext().getApplicationInfo(); + } + } + + protected class DelegatingTestContentProvider extends ContentProvider { + ContentProvider mTargetProvider; + + 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 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; + } + } + + /* + * A MockContentResolver that records each URI that is supplied to + * notifyChange. Warning: the list of changed URIs is not + * synchronized. + */ + protected class ChangeRecordingMockContentResolver extends MockContentResolver { + public final LinkedList notifyChangeList = new LinkedList(); + + @Override + public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { + notifyChangeList.addLast(uri); + + super.notifyChange(uri, observer, syncToNetwork); + } + } + + /** + * Factory function that makes new ContentProvider instances. + *

+ * We want a fresh provider each test, so this should be invoked in + * setUp before each individual test. + */ + protected static Callable sBrowserProviderCallable = new Callable() { + @Override + public ContentProvider call() { + return new BrowserProvider(); + } + }; + + private void setUpContentProvider(ContentProvider targetProvider) throws Exception { + mResolver = new ChangeRecordingMockContentResolver(); + + final String filenamePrefix = this.getClass().getSimpleName() + "."; + RenamingDelegatingContext targetContextWrapper = + new RenamingDelegatingContext( + new ContentProviderMockContext(), + (Context)getActivity(), + filenamePrefix); + + mProviderContext = new IsolatedContext(mResolver, targetContextWrapper); + + targetProvider.attachInfo(mProviderContext, null); + + mProvider = new DelegatingTestContentProvider(targetProvider); + mProvider.attachInfo(mProviderContext, null); + + mResolver.addProvider(mProviderAuthority, mProvider); + } + + public static Uri appendUriParam(Uri uri, String param, String value) { + return uri.buildUpon().appendQueryParameter(param, value).build(); + } + + public void setTestName(String testName) { + mAsserter.setTestName(this.getClass().getName() + " - " + testName); + } + + @Override + public void setUp() throws Exception { + throw new UnsupportedOperationException("You should call setUp(authority, databaseName) instead"); + } + + public void setUp(Callable contentProviderFactory, String authority, String databaseName) throws Exception { + super.setUp(); + + mTests = new ArrayList(); + mDatabaseName = databaseName; + + mProviderAuthority = authority; + + setUpContentProvider(contentProviderFactory.call()); + } + + @Override + public void tearDown() throws Exception { + if (Build.VERSION.SDK_INT >= 11) { + mProvider.shutdown(); + } + + if (mDatabaseName != null) { + mProviderContext.deleteDatabase(mDatabaseName); + } + + super.tearDown(); + } + + public AssetManager getAssetManager() { + return getInstrumentation().getContext().getAssets(); + } +}