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

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