From 97cc279d9926daf3e6efaf26b839587378a788ab Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 26 Jan 2016 08:49:23 +1100 Subject: [PATCH] Fix unit tests to work with a singleton DBHelper instance. The fix for the database locking bug was to have a singleton `DBHelper` instance. This breaks tests because multiple tests share the same database. The solution is to: * Hack together a static method which clears the singleton, then invoke it in the `setUp()` method for relevant test cases. * Ensure the mock context provided to the providers during the tests is able to provide a context via `getApplicationContext()`. Without this, the mock context throws an `UnsupportedOperationException` when invoking this method. --- .../src/org/fdroid/fdroid/data/FDroidProvider.java | 7 +++++++ .../android/test/ProviderTestCase2MockContext.java | 13 +++++++++++-- .../src/mock/MockContextSwappableComponents.java | 2 +- .../src/org/fdroid/fdroid/FDroidProviderTest.java | 2 ++ .../src/org/fdroid/fdroid/MultiRepoUpdaterTest.java | 9 +++++++++ 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/F-Droid/src/org/fdroid/fdroid/data/FDroidProvider.java b/F-Droid/src/org/fdroid/fdroid/data/FDroidProvider.java index 42936570d..9153628db 100644 --- a/F-Droid/src/org/fdroid/fdroid/data/FDroidProvider.java +++ b/F-Droid/src/org/fdroid/fdroid/data/FDroidProvider.java @@ -72,6 +72,13 @@ public abstract class FDroidProvider extends ContentProvider { return result; } + /** + * Only used for testing. Not quite sure how to mock a singleton variable like this. + */ + public static void clearDbHelperSingleton() { + dbHelper = null; + } + private static synchronized DBHelper getOrCreateDb(Context context) { if (dbHelper == null) { Utils.debugLog(TAG, "First time accessing database, creating new helper"); diff --git a/F-Droid/test/src/android/test/ProviderTestCase2MockContext.java b/F-Droid/test/src/android/test/ProviderTestCase2MockContext.java index 54ad5f97f..71152507a 100644 --- a/F-Droid/test/src/android/test/ProviderTestCase2MockContext.java +++ b/F-Droid/test/src/android/test/ProviderTestCase2MockContext.java @@ -20,6 +20,7 @@ import android.annotation.TargetApi; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; +import android.content.ContextWrapper; import android.content.res.Resources; import android.database.DatabaseUtils; import android.os.Build; @@ -136,12 +137,20 @@ public abstract class ProviderTestCase2MockContext ex mResolver = new MockContentResolver(); final String filenamePrefix = "test."; - RenamingDelegatingContext targetContextWrapper = new + final RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext( createMockContext(new MockContext2()), // The context that most methods are delegated to getContext(), // The context that file methods are delegated to filenamePrefix); - mProviderContext = new IsolatedContext(mResolver, targetContextWrapper); + + mProviderContext = new IsolatedContext(mResolver, new ContextWrapper(targetContextWrapper) { + // The FDroidProvider class needs access to an application context in order to initialize + // the singleton DBHelper instance. + @Override + public Context getApplicationContext() { + return targetContextWrapper; + } + }); mProvider = mProviderClass.newInstance(); mProvider.attachInfo(mProviderContext, null); diff --git a/F-Droid/test/src/mock/MockContextSwappableComponents.java b/F-Droid/test/src/mock/MockContextSwappableComponents.java index 20578915c..9be5a6265 100644 --- a/F-Droid/test/src/mock/MockContextSwappableComponents.java +++ b/F-Droid/test/src/mock/MockContextSwappableComponents.java @@ -1,5 +1,6 @@ package mock; +import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; import android.test.mock.MockContentResolver; @@ -8,7 +9,6 @@ import android.test.mock.MockContext; public class MockContextSwappableComponents extends MockContext { private PackageManager packageManager; - private Resources resources; private MockContentResolver contentResolver; diff --git a/F-Droid/test/src/org/fdroid/fdroid/FDroidProviderTest.java b/F-Droid/test/src/org/fdroid/fdroid/FDroidProviderTest.java index 6849022e9..3bf2ea8bb 100644 --- a/F-Droid/test/src/org/fdroid/fdroid/FDroidProviderTest.java +++ b/F-Droid/test/src/org/fdroid/fdroid/FDroidProviderTest.java @@ -45,6 +45,8 @@ public abstract class FDroidProviderTest extends Provi public void setUp() throws Exception { super.setUp(); + FDroidProvider.clearDbHelperSingleton(); + // Instantiate all providers other than the one which was already created by the base class. // This is because F-Droid providers tend to perform joins onto tables managed by other // providers, and so we need to be able to insert into those other providers for these diff --git a/F-Droid/test/src/org/fdroid/fdroid/MultiRepoUpdaterTest.java b/F-Droid/test/src/org/fdroid/fdroid/MultiRepoUpdaterTest.java index 46571b182..d60589f89 100644 --- a/F-Droid/test/src/org/fdroid/fdroid/MultiRepoUpdaterTest.java +++ b/F-Droid/test/src/org/fdroid/fdroid/MultiRepoUpdaterTest.java @@ -18,6 +18,7 @@ import org.fdroid.fdroid.RepoUpdater.UpdateException; import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.ApkProvider; import org.fdroid.fdroid.data.AppProvider; +import org.fdroid.fdroid.data.FDroidProvider; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.TempApkProvider; @@ -122,12 +123,20 @@ public class MultiRepoUpdaterTest extends InstrumentationTestCase { public File getDatabasePath(String name) { return new File(getInstrumentation().getContext().getFilesDir(), "fdroid_test.db"); } + + @Override + public Context getApplicationContext() { + // Used by the DBHelper singleton instance. + return this; + } } @Override public void setUp() throws Exception { super.setUp(); + FDroidProvider.clearDbHelperSingleton(); + context = new TestContext(); testFilesDir = TestUtils.getWriteableDir(getInstrumentation());