diff --git a/app/build.gradle b/app/build.gradle
index 8fd669d5d..a9bf227f2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -41,6 +41,11 @@ dependencies {
testCompile 'junit:junit:4.12'
+ // 3.1-rc1 is required because it is the first to implements API v23.
+ testCompile "org.robolectric:robolectric:3.1-rc1"
+
+ testCompile "org.mockito:mockito-core:1.10.19"
+
androidTestCompile 'com.android.support:support-annotations:23.4.0'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test:rules:0.5'
@@ -225,13 +230,22 @@ pmd {
consoleOutput = true
}
-task pmd(type: Pmd, dependsOn: assembleDebug) {
- ruleSetFiles = files("${project.rootDir}/config/pmd/rules.xml")
+task pmdMain(type: Pmd, dependsOn: assembleDebug) {
+ ruleSetFiles = files("${project.rootDir}/config/pmd/rules.xml", "${project.rootDir}/config/pmd/rules-main.xml")
ruleSets = [] // otherwise defaults clash with the list in rules.xml
- source 'src/main/java', 'src/test/java', 'src/androidTest/java'
+ source 'src/main/java'
include '**/*.java'
}
+task pmdTest(type: Pmd, dependsOn: assembleDebug) {
+ ruleSetFiles = files("${project.rootDir}/config/pmd/rules.xml", "${project.rootDir}/config/pmd/rules-test.xml")
+ ruleSets = [] // otherwise defaults clash with the list in rules.xml
+ source 'src/test/java', 'src/androidTest/java'
+ include '**/*.java'
+}
+
+task pmd(dependsOn: [pmdMain, pmdTest]) {}
+
// This person took the example code below from another blogpost online, however
// I lost the reference to it:
// http://stackoverflow.com/questions/23297562/gradle-javadoc-and-android-documentation
diff --git a/app/src/androidTest/assets/simpleIndex.jar b/app/src/androidTest/assets/simpleIndex.jar
index 1c173ceb3..e69de29bb 100644
Binary files a/app/src/androidTest/assets/simpleIndex.jar and b/app/src/androidTest/assets/simpleIndex.jar differ
diff --git a/app/src/androidTest/java/android/test/ProviderTestCase2MockContext.java b/app/src/androidTest/java/android/test/ProviderTestCase2MockContext.java
deleted file mode 100644
index 405902649..000000000
--- a/app/src/androidTest/java/android/test/ProviderTestCase2MockContext.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.test;
-
-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;
-import android.test.mock.MockContentResolver;
-import android.test.mock.MockContext;
-
-import org.junit.After;
-import org.junit.Before;
-
-import java.io.File;
-
-/**
- * This test case class provides a framework for testing a single
- * {@link ContentProvider} and for testing your app code with an
- * isolated content provider. Instead of using the system map of
- * providers that is based on the manifests of other applications, the test
- * case creates its own internal map. It then uses this map to resolve providers
- * given an authority. This allows you to inject test providers and to null out
- * providers that you do not want to use.
- *
- * This test case also sets up the following mock objects:
- *
- *
- * -
- * An {@link android.test.IsolatedContext} that stubs out Context methods that might
- * affect the rest of the running system, while allowing tests to do real file and
- * database work.
- *
- * -
- * A {@link android.test.mock.MockContentResolver} that provides the functionality of a
- * regular content resolver, but uses {@link IsolatedContext}. It stubs out
- * {@link ContentResolver#notifyChange(Uri, ContentObserver, boolean)} to
- * prevent the test from affecting the running system.
- *
- * -
- * An instance of the provider under test, running in an {@link IsolatedContext}.
- *
- *
- *
- * This framework is set up automatically by the base class' {@link #setUp()} method. If you
- * override this method, you must call the super method as the first statement in
- * your override.
- *
- *
- * In order for their tests to be run, concrete subclasses must provide their own
- * constructor with no arguments. This constructor must call
- * {@link #ProviderTestCase2MockContext(Class, String)} as its first operation.
- *
- * For more information on content provider testing, please see
- * Content Provider Testing.
- */
-public abstract class ProviderTestCase2MockContext extends AndroidTestCase {
-
- Class mProviderClass;
- String mProviderAuthority;
-
- private IsolatedContext mProviderContext;
- private MockContentResolver mResolver;
-
- private class MockContext2 extends MockContext {
-
- @Override
- public Resources getResources() {
- return getContext().getResources();
- }
-
- @Override
- public File getDir(String name, int mode) {
- // name the directory so the directory will be separated from
- // one created through the regular Context
- return getContext().getDir("mockcontext2_" + name, mode);
- }
-
- @Override
- public Context getApplicationContext() {
- return this;
- }
-
- @Override
- public String getPackageName() {
- return "org.fdroid.fdroid";
- }
- }
-
- /**
- * Constructor.
- *
- * @param providerClass The class name of the provider under test
- * @param providerAuthority The provider's authority string
- */
- public ProviderTestCase2MockContext(Class providerClass, String providerAuthority) {
- mProviderClass = providerClass;
- mProviderAuthority = providerAuthority;
- }
-
- private T mProvider;
-
- /**
- * Returns the content provider created by this class in the {@link #setUp()} method.
- * @return T An instance of the provider class given as a parameter to the test case class.
- */
- public T getProvider() {
- return mProvider;
- }
-
- protected abstract Context createMockContext(Context delegate);
-
- /**
- * Sets up the environment for the test fixture.
- *
- * Creates a new
- * {@link android.test.mock.MockContentResolver}, a new IsolatedContext
- * that isolates the provider's file operations, and a new instance of
- * the provider under test within the isolated environment.
- *
- *
- * @throws Exception
- */
- @Override
- @Before
- protected void setUp() throws Exception {
- super.setUp();
-
- mResolver = new MockContentResolver();
- final String filenamePrefix = "test.";
- 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, 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);
- assertNotNull(mProvider);
- mResolver.addProvider(mProviderAuthority, getProvider());
- }
-
- /**
- * Tears down the environment for the test fixture.
- *
- * Calls {@link android.content.ContentProvider#shutdown()} on the
- * {@link android.content.ContentProvider} represented by mProvider.
- */
- @Override
- @After
- protected void tearDown() throws Exception {
- shutdownProvider();
- super.tearDown();
- }
-
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- private void shutdownProvider() {
- if (Build.VERSION.SDK_INT >= 11) {
- mProvider.shutdown();
- }
- }
-
- /**
- * Gets the {@link MockContentResolver} created by this class during initialization. You
- * must use the methods of this resolver to access the provider under test.
- *
- * @return A {@link MockContentResolver} instance.
- */
- public MockContentResolver getMockContentResolver() {
- return mResolver;
- }
-
- /**
- * Gets the {@link IsolatedContext} created by this class during initialization.
- * @return The {@link IsolatedContext} instance
- */
- public IsolatedContext getMockContext() {
- return mProviderContext;
- }
-
- /**
- *
- * Creates a new content provider of the same type as that passed to the test case class,
- * with an authority name set to the authority parameter, and using an SQLite database as
- * the underlying data source. The SQL statement parameter is used to create the database.
- * This method also creates a new {@link MockContentResolver} and adds the provider to it.
- *
- *
- * Both the new provider and the new resolver are put into an {@link IsolatedContext}
- * that uses the targetContext parameter for file operations and a {@link MockContext}
- * for everything else. The IsolatedContext prepends the filenamePrefix parameter to
- * file, database, and directory names.
- *
- *
- * This is a convenience method for creating a "mock" provider that can contain test data.
- *
- *
- * @param targetContext The context to use as the basis of the IsolatedContext
- * @param filenamePrefix A string that is prepended to file, database, and directory names
- * @param providerClass The type of the provider being tested
- * @param authority The authority string to associated with the test provider
- * @param databaseName The name assigned to the database
- * @param databaseVersion The version assigned to the database
- * @param sql A string containing the SQL statements that are needed to create the desired
- * database and its tables. The format is the same as that generated by the
- * sqlite3 tool's .dump
command.
- * @return ContentResolver A new {@link MockContentResolver} linked to the provider
- *
- * @throws IllegalAccessException
- * @throws InstantiationException
- */
- public static ContentResolver newResolverWithContentProviderFromSql(
- Context targetContext, String filenamePrefix, Class providerClass, String authority,
- String databaseName, int databaseVersion, String sql)
- throws IllegalAccessException, InstantiationException {
- MockContentResolver resolver = new MockContentResolver();
- RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext(
- new MockContext(), // The context that most methods are delegated to
- targetContext, // The context that file methods are delegated to
- filenamePrefix);
- Context context = new IsolatedContext(resolver, targetContextWrapper);
- DatabaseUtils.createDbFromSqlStatements(context, databaseName, databaseVersion, sql);
-
- T provider = providerClass.newInstance();
- provider.attachInfo(context, null);
- resolver.addProvider(authority, provider);
-
- return resolver;
- }
-}
diff --git a/app/src/androidTest/java/mock/MockApplicationInfo.java b/app/src/androidTest/java/mock/MockApplicationInfo.java
deleted file mode 100644
index 1ab4592a4..000000000
--- a/app/src/androidTest/java/mock/MockApplicationInfo.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package mock;
-
-import android.annotation.SuppressLint;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-
-import java.io.File;
-import java.io.IOException;
-
-@SuppressLint("ParcelCreator")
-public class MockApplicationInfo extends ApplicationInfo {
-
- private final PackageInfo info;
-
- public MockApplicationInfo(PackageInfo info) {
- this.info = info;
- try {
- this.publicSourceDir = File.createTempFile(info.packageName, "apk").getAbsolutePath();
- } catch (IOException e) {
- this.publicSourceDir = "/data/app/" + info.packageName + "-4.apk";
- }
- }
-
- @Override
- public CharSequence loadLabel(PackageManager pm) {
- return "Mock app: " + info.packageName;
- }
-}
diff --git a/app/src/androidTest/java/mock/MockCategoryResources.java b/app/src/androidTest/java/mock/MockCategoryResources.java
deleted file mode 100644
index 0f88ade9f..000000000
--- a/app/src/androidTest/java/mock/MockCategoryResources.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package mock;
-
-import android.content.Context;
-
-import org.fdroid.fdroid.R;
-
-public class MockCategoryResources extends MockFDroidResources {
-
- public MockCategoryResources(Context getStringDelegatingContext) {
- super(getStringDelegatingContext);
- }
-
- @Override
- public String getString(int id) {
- switch (id) {
- case R.string.category_All:
- return "All";
- case R.string.category_Recently_Updated:
- return "Recently Updated";
- case R.string.category_Whats_New:
- return "Whats New";
- default:
- return "";
- }
- }
-
-}
diff --git a/app/src/androidTest/java/mock/MockContextEmptyComponents.java b/app/src/androidTest/java/mock/MockContextEmptyComponents.java
deleted file mode 100644
index eb962bbe9..000000000
--- a/app/src/androidTest/java/mock/MockContextEmptyComponents.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package mock;
-
-/**
- * As more components are required to test different parts of F-Droid, we can
- * create them and add them here (and accessors to the parent class).
- */
-public class MockContextEmptyComponents extends MockContextSwappableComponents {
-
- public MockContextEmptyComponents() {
- setPackageManager(new MockEmptyPackageManager());
- setResources(new MockEmptyResources());
- }
-
-}
diff --git a/app/src/androidTest/java/mock/MockContextSwappableComponents.java b/app/src/androidTest/java/mock/MockContextSwappableComponents.java
deleted file mode 100644
index 5d94967f2..000000000
--- a/app/src/androidTest/java/mock/MockContextSwappableComponents.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package mock;
-
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.test.mock.MockContentResolver;
-import android.test.mock.MockContext;
-
-public class MockContextSwappableComponents extends MockContext {
-
- private PackageManager packageManager;
- private Resources resources;
- private MockContentResolver contentResolver;
-
- public MockContextSwappableComponents setPackageManager(PackageManager pm) {
- packageManager = pm;
- return this;
- }
-
- public MockContextSwappableComponents setResources(Resources resources) {
- this.resources = resources;
- return this;
- }
-
- public MockContextSwappableComponents setContentResolver(MockContentResolver contentResolver) {
- this.contentResolver = contentResolver;
- return this;
- }
-
- @Override
- public PackageManager getPackageManager() {
- return packageManager;
- }
-
- @Override
- public Resources getResources() {
- return resources;
- }
-
- @Override
- public MockContentResolver getContentResolver() {
- return contentResolver;
- }
-}
diff --git a/app/src/androidTest/java/mock/MockEmptyPackageManager.java b/app/src/androidTest/java/mock/MockEmptyPackageManager.java
deleted file mode 100644
index 463864179..000000000
--- a/app/src/androidTest/java/mock/MockEmptyPackageManager.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package mock;
-
-import android.content.pm.PackageInfo;
-import android.test.mock.MockPackageManager;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class MockEmptyPackageManager extends MockPackageManager {
-
- @Override
- public List getInstalledPackages(int flags) {
- return new ArrayList<>();
- }
-
-}
diff --git a/app/src/androidTest/java/mock/MockEmptyResources.java b/app/src/androidTest/java/mock/MockEmptyResources.java
deleted file mode 100644
index fdc06e47f..000000000
--- a/app/src/androidTest/java/mock/MockEmptyResources.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package mock;
-
-import android.test.mock.MockResources;
-
-public class MockEmptyResources extends MockResources {
-
- @Override
- public String getString(int id) {
- return "";
- }
-
-}
diff --git a/app/src/androidTest/java/mock/MockFDroidResources.java b/app/src/androidTest/java/mock/MockFDroidResources.java
deleted file mode 100644
index 5cb33798c..000000000
--- a/app/src/androidTest/java/mock/MockFDroidResources.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package mock;
-
-import android.content.Context;
-import android.test.mock.MockResources;
-
-import org.fdroid.fdroid.R;
-
-public class MockFDroidResources extends MockResources {
-
- private final Context getStringDelegatingContext;
-
- public MockFDroidResources(Context getStringDelegatingContext) {
- this.getStringDelegatingContext = getStringDelegatingContext;
- }
-
- @Override
- public String getString(int id) {
- return getStringDelegatingContext.getString(id);
- }
-
- @Override
- public int getInteger(int id) {
- switch (id) {
- case R.integer.fdroid_repo_inuse:
- return 1;
- case R.integer.fdroid_archive_inuse:
- return 0;
- case R.integer.fdroid_repo_priority:
- return 10;
- case R.integer.fdroid_archive_priority:
- return 20;
- default:
- return 0;
- }
- }
-
-}
diff --git a/app/src/androidTest/java/mock/MockInstallablePackageManager.java b/app/src/androidTest/java/mock/MockInstallablePackageManager.java
deleted file mode 100644
index 1fcef1e86..000000000
--- a/app/src/androidTest/java/mock/MockInstallablePackageManager.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package mock;
-
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.test.mock.MockPackageManager;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-public class MockInstallablePackageManager extends MockPackageManager {
-
- private final List info = new ArrayList<>();
-
- @Override
- public List getInstalledPackages(int flags) {
- return info;
- }
-
- @Override
- public PackageInfo getPackageInfo(String id, int flags) {
- for (PackageInfo i : info) {
- if (i.packageName.equals(id)) {
- return i;
- }
- }
- return null;
- }
-
- public void install(String id, int version, String versionName) {
- PackageInfo existing = getPackageInfo(id, 0);
- if (existing != null) {
- existing.versionCode = version;
- existing.versionName = versionName;
- } else {
- PackageInfo p = new PackageInfo();
- p.packageName = id;
- p.versionCode = version;
- p.versionName = versionName;
- p.applicationInfo = new MockApplicationInfo(p);
- p.lastUpdateTime = System.currentTimeMillis();
- info.add(p);
- }
- }
-
- @Override
- public ApplicationInfo getApplicationInfo(String packageName, int flags) throws NameNotFoundException {
- return new MockApplicationInfo(getPackageInfo(packageName, 0));
- }
-
- public void remove(String id) {
- for (Iterator it = info.iterator(); it.hasNext();) {
- PackageInfo info = it.next();
- if (info.packageName.equals(id)) {
- it.remove();
- return;
- }
- }
- }
-
-}
diff --git a/app/src/androidTest/java/org/fdroid/fdroid/FileCompatTest.java b/app/src/androidTest/java/org/fdroid/fdroid/FileCompatTest.java
index 819d8539b..18456f749 100644
--- a/app/src/androidTest/java/org/fdroid/fdroid/FileCompatTest.java
+++ b/app/src/androidTest/java/org/fdroid/fdroid/FileCompatTest.java
@@ -1,7 +1,10 @@
package org.fdroid.fdroid;
import android.app.Instrumentation;
+import android.content.Context;
import android.os.Build;
+import android.os.Environment;
+import android.support.annotation.Nullable;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
@@ -14,10 +17,15 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.util.UUID;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
/**
@@ -36,8 +44,8 @@ public class FileCompatTest {
@Before
public void setUp() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- File dir = TestUtils.getWriteableDir(instrumentation);
- sourceFile = SanitizedFile.knownSanitized(TestUtils.copyAssetToDir(instrumentation.getContext(), "simpleIndex.jar", dir));
+ File dir = getWriteableDir(instrumentation);
+ sourceFile = SanitizedFile.knownSanitized(copyAssetToDir(instrumentation.getContext(), "simpleIndex.jar", dir));
destFile = new SanitizedFile(dir, "dest-" + UUID.randomUUID() + ".testproduct");
assertFalse(destFile.exists());
assertTrue(sourceFile.getAbsolutePath() + " should exist.", sourceFile.exists());
@@ -62,24 +70,70 @@ public class FileCompatTest {
@Test
public void testSymlinkLibcore() {
-
- if (Build.VERSION.SDK_INT >= 19) {
- FileCompatForTest.symlinkLibcoreTest(sourceFile, destFile);
- assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
- } else {
- Log.w(TAG, "Cannot test symlink-libcore on this device. Requires android-19, but this has android-" + Build.VERSION.SDK_INT);
- }
+ assumeTrue(Build.VERSION.SDK_INT >= 19);
+ FileCompatForTest.symlinkLibcoreTest(sourceFile, destFile);
+ assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
}
@Test
public void testSymlinkOs() {
-
- if (Build.VERSION.SDK_INT >= 21) {
- FileCompatForTest.symlinkOsTest(sourceFile, destFile);
- assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
- } else {
- Log.w(TAG, "Cannot test symlink-os on this device. Requires android-21, but only has android-" + Build.VERSION.SDK_INT);
- }
+ assumeTrue(Build.VERSION.SDK_INT >= 21);
+ FileCompatForTest.symlinkOsTest(sourceFile, destFile);
+ assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
}
+ @Nullable
+ private static File copyAssetToDir(Context context, String assetName, File directory) {
+ File tempFile;
+ InputStream input = null;
+ OutputStream output = null;
+ try {
+ tempFile = File.createTempFile(assetName + "-", ".testasset", directory);
+ Log.i(TAG, "Copying asset file " + assetName + " to directory " + directory);
+ input = context.getAssets().open(assetName);
+ output = new FileOutputStream(tempFile);
+ Utils.copy(input, output);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ } finally {
+ Utils.closeQuietly(output);
+ Utils.closeQuietly(input);
+ }
+ return tempFile;
+ }
+
+ /**
+ * Prefer internal over external storage, because external tends to be FAT filesystems,
+ * which don't support symlinks (which we test using this method).
+ */
+ private static File getWriteableDir(Instrumentation instrumentation) {
+ Context context = instrumentation.getContext();
+ Context targetContext = instrumentation.getTargetContext();
+
+ File[] dirsToTry = new File[]{
+ context.getCacheDir(),
+ context.getFilesDir(),
+ targetContext.getCacheDir(),
+ targetContext.getFilesDir(),
+ context.getExternalCacheDir(),
+ context.getExternalFilesDir(null),
+ targetContext.getExternalCacheDir(),
+ targetContext.getExternalFilesDir(null),
+ Environment.getExternalStorageDirectory(),
+ };
+
+ return getWriteableDir(dirsToTry);
+ }
+
+ private static File getWriteableDir(File[] dirsToTry) {
+
+ for (File dir : dirsToTry) {
+ if (dir != null && dir.canWrite()) {
+ return dir;
+ }
+ }
+
+ return null;
+ }
}
diff --git a/app/src/androidTest/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java b/app/src/androidTest/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java
deleted file mode 100644
index 9fc9c9443..000000000
--- a/app/src/androidTest/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java
+++ /dev/null
@@ -1,446 +0,0 @@
-
-package org.fdroid.fdroid;
-
-import android.content.ContentProvider;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.res.AssetManager;
-import android.content.res.Resources;
-import android.support.annotation.NonNull;
-import android.test.InstrumentationTestCase;
-import android.test.RenamingDelegatingContext;
-import android.test.mock.MockContentResolver;
-import android.text.TextUtils;
-import android.util.Log;
-
-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;
-import org.fdroid.fdroid.data.TempAppProvider;
-
-import java.io.File;
-import java.util.List;
-import java.util.UUID;
-
-@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
-public class MultiRepoUpdaterTest extends InstrumentationTestCase {
- private static final String TAG = "MultiRepoUpdaterTest";
-
- private static final String REPO_MAIN = "Test F-Droid repo";
- private static final String REPO_ARCHIVE = "Test F-Droid repo (Archive)";
- private static final String REPO_CONFLICTING = "Test F-Droid repo with different apps";
-
- private Context context;
- private RepoUpdater conflictingRepoUpdater;
- private RepoUpdater mainRepoUpdater;
- private RepoUpdater archiveRepoUpdater;
- private File testFilesDir;
-
- private static final String PUB_KEY =
- "3082050b308202f3a003020102020420d8f212300d06092a864886f70d01010b050030363110300e0603" +
- "55040b1307462d44726f69643122302006035504031319657073696c6f6e2e70657465722e7365727779" +
- "6c6f2e636f6d301e170d3135303931323233313632315a170d3433303132383233313632315a30363110" +
- "300e060355040b1307462d44726f69643122302006035504031319657073696c6f6e2e70657465722e73" +
- "657277796c6f2e636f6d30820222300d06092a864886f70d01010105000382020f003082020a02820201" +
- "00b21fe72b84ce721967851364bd20511088d117bc3034e4bb4d3c1a06af2a308fdffdaf63b12e0926b9" +
- "0545134b9ff570646cbcad89d9e86dcc8eb9977dd394240c75bccf5e8ddc3c5ef91b4f16eca5f36c36f1" +
- "92463ff2c9257d3053b7c9ecdd1661bd01ec3fe70ee34a7e6b92ddba04f258a32d0cfb1b0ce85d047180" +
- "97fc4bdfb54541b430dfcfc1c84458f9eb5627e0ec5341d561c3f15f228379a1282d241329198f31a7ac" +
- "cd51ab2bbb881a1da55001123483512f77275f8990c872601198065b4e0137ddd1482e4fdefc73b857d4" +
- "be324ca96c268ceb725398f8cc38a0dc6aa2c277f8686724e8c7ff3f320a05791fccacc6caa956cf23a9" +
- "de2dc7070b262c0e35d90d17e90773bb11e875e79a8dfd958e359d5d5ad903a7cbc2955102502bd0134c" +
- "a1ff7a0bbbbb57302e4a251e40724dcaa8ad024f4b3a71b8fceaac664c0dcc1995a1c4cf42676edad8bc" +
- "b03ba255ab796677f18fff2298e1aaa5b134254b44d08a4d934c9859af7bbaf078c37b7f628db0e2cffb" +
- "0493a669d5f4770d35d71284550ce06d6f6811cd2a31585085716257a4ba08ad968b0a2bf88f34ca2f2c" +
- "73af1c042ab147597faccfb6516ef4468cfa0c5ab3c8120eaa7bac1080e4d2310f717db20815d0e1ee26" +
- "bd4e47eed8d790892017ae9595365992efa1b7fd1bc1963f018264b2b3749b8f7b1907bb0843f1e7fc2d" +
- "3f3b02284cd4bae0ab0203010001a321301f301d0603551d0e0416041456110e4fed863ab1df9448bfd9" +
- "e10a8bc32ffe08300d06092a864886f70d01010b050003820201008082572ae930ebc55ecf1110f4bb72" +
- "ad2a952c8ac6e65bd933706beb4a310e23deabb8ef6a7e93eea8217ab1f3f57b1f477f95f1d62eccb563" +
- "67a4d70dfa6fcd2aace2bb00b90af39412a9441a9fae2396ff8b93de1df3d9837c599b1f80b7d75285cb" +
- "df4539d7dd9612f54b45ca59bc3041c9b92fac12753fac154d12f31df360079ab69a2d20db9f6a7277a8" +
- "259035e93de95e8cbc80351bc83dd24256183ea5e3e1db2a51ea314cdbc120c064b77e2eb3a731530511" +
- "1e1dabed6996eb339b7cb948d05c1a84d63094b4a4c6d11389b2a7b5f2d7ecc9a149dda6c33705ef2249" +
- "58afdfa1d98cf646dcf8857cd8342b1e07d62cb4313f35ad209046a4a42ff73f38cc740b1e695eeda49d" +
- "5ea0384ad32f9e3ae54f6a48a558dbc7cccabd4e2b2286dc9c804c840bd02b9937841a0e48db00be9e3c" +
- "d7120cf0f8648ce4ed63923f0352a2a7b3b97fc55ba67a7a218b8c0b3cda4a45861280a622e0a59cc9fb" +
- "ca1117568126c581afa4408b0f5c50293c212c406b8ab8f50aad5ed0f038cfca580ef3aba7df25464d9e" +
- "495ffb629922cfb511d45e6294c045041132452f1ed0f20ac3ab4792f610de1734e4c8b71d743c4b0101" +
- "98f848e0dbfce5a0f2da0198c47e6935a47fda12c518ef45adfb66ddf5aebaab13948a66c004b8592d22" +
- "e8af60597c4ae2977977cf61dc715a572e241ae717cafdb4f71781943945ac52e0f50b";
-
- public class TestContext extends RenamingDelegatingContext {
-
- private MockContentResolver resolver;
-
- public TestContext() {
- super(getInstrumentation().getTargetContext(), "test.");
-
- resolver = new MockContentResolver();
- resolver.addProvider(AppProvider.getAuthority(), prepareProvider(new AppProvider()));
- resolver.addProvider(ApkProvider.getAuthority(), prepareProvider(new ApkProvider()));
- resolver.addProvider(RepoProvider.getAuthority(), prepareProvider(new RepoProvider()));
- resolver.addProvider(TempAppProvider.getAuthority(), prepareProvider(new TempAppProvider()));
- resolver.addProvider(TempApkProvider.getAuthority(), prepareProvider(new TempApkProvider()));
- }
-
- private ContentProvider prepareProvider(ContentProvider provider) {
- provider.attachInfo(this, null);
- provider.onCreate();
- return provider;
- }
-
- @Override
- public File getFilesDir() {
- return getInstrumentation().getTargetContext().getFilesDir();
- }
-
- /**
- * String resources used during testing (e.g. when bootstraping the database) are from
- * the real org.fdroid.fdroid app, not the test org.fdroid.fdroid.test app.
- */
- @Override
- public Resources getResources() {
- return getInstrumentation().getTargetContext().getResources();
- }
-
- @Override
- public ContentResolver getContentResolver() {
- return resolver;
- }
-
- @Override
- public AssetManager getAssets() {
- return getInstrumentation().getContext().getAssets();
- }
-
- @Override
- 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());
-
- // On a fresh database install, there will be F-Droid + GP repos, including their Archive
- // repos that we are not interested in.
- RepoProvider.Helper.remove(context, 1);
- RepoProvider.Helper.remove(context, 2);
- RepoProvider.Helper.remove(context, 3);
- RepoProvider.Helper.remove(context, 4);
-
- conflictingRepoUpdater = createUpdater(REPO_CONFLICTING, context);
- mainRepoUpdater = createUpdater(REPO_MAIN, context);
- archiveRepoUpdater = createUpdater(REPO_ARCHIVE, context);
- }
-
- /**
- * Check that all of the expected apps and apk versions are available in the database. This
- * check will take into account the repository the apks came from, to ensure that each
- * repository indeed contains the apks that it said it would provide.
- */
- private void assertExpected() {
- Log.i(TAG, "Asserting all versions of each .apk are in index.");
- List repos = RepoProvider.Helper.all(context);
- assertEquals("Repos", 3, repos.size());
-
- assertMainRepo(repos);
- assertMainArchiveRepo(repos);
- assertConflictingRepo(repos);
- }
-
- /**
- *
- */
- private void assertSomewhatAcceptable() {
- Log.i(TAG, "Asserting at least one versions of each .apk is in index.");
- List repos = RepoProvider.Helper.all(context);
- assertEquals("Repos", 3, repos.size());
-
- assertApp2048();
- assertAppAdaway();
- assertAppAdbWireless();
- assertAppIcsImport();
- }
-
- private void assertApp(String packageName, int[] versionCodes) {
- List apks = ApkProvider.Helper.findByPackageName(context, packageName, ApkProvider.DataColumns.ALL);
- assertApksExist(apks, packageName, versionCodes);
- }
-
- private void assertApp2048() {
- assertApp("com.uberspot.a2048", new int[]{19, 18});
- }
-
- private void assertAppAdaway() {
- assertApp("org.adaway", new int[]{54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 42, 40, 38, 37, 36, 35});
- }
-
- private void assertAppAdbWireless() {
- assertApp("siir.es.adbWireless", new int[]{12});
- }
-
- private void assertAppIcsImport() {
- assertApp("org.dgtale.icsimport", new int[]{3, 2});
- }
-
- /**
- * + 2048 (com.uberspot.a2048)
- * - Version 1.96 (19)
- * - Version 1.95 (18)
- * + AdAway (org.adaway)
- * - Version 3.0.2 (54)
- * - Version 3.0.1 (53)
- * - Version 3.0 (52)
- * + adbWireless (siir.es.adbWireless)
- * - Version 1.5.4 (12)
- */
- private void assertMainRepo(List allRepos) {
- Repo repo = findRepo(REPO_MAIN, allRepos);
-
- List apks = ApkProvider.Helper.findByRepo(context, repo, ApkProvider.DataColumns.ALL);
- assertEquals("Apks for main repo", apks.size(), 6);
- assertApksExist(apks, "com.uberspot.a2048", new int[]{18, 19});
- assertApksExist(apks, "org.adaway", new int[]{52, 53, 54});
- assertApksExist(apks, "siir.es.adbWireless", new int[]{12});
- }
-
- /**
- * + AdAway (org.adaway)
- * - Version 2.9.2 (51)
- * - Version 2.9.1 (50)
- * - Version 2.9 (49)
- * - Version 2.8.1 (48)
- * - Version 2.8 (47)
- * - Version 2.7 (46)
- * - Version 2.6 (45)
- * - Version 2.3 (42)
- * - Version 2.1 (40)
- * - Version 1.37 (38)
- * - Version 1.36 (37)
- * - Version 1.35 (36)
- * - Version 1.34 (35)
- */
- private void assertMainArchiveRepo(List allRepos) {
- Repo repo = findRepo(REPO_ARCHIVE, allRepos);
-
- List apks = ApkProvider.Helper.findByRepo(context, repo, ApkProvider.DataColumns.ALL);
- assertEquals("Apks for main archive repo", 13, apks.size());
- assertApksExist(apks, "org.adaway", new int[]{35, 36, 37, 38, 40, 42, 45, 46, 47, 48, 49, 50, 51});
- }
-
- /**
- * + AdAway (org.adaway)
- * - Version 3.0.1 (53) *
- * - Version 3.0 (52) *
- * - Version 2.9.2 (51) *
- * - Version 2.2.1 (50) *
- * + Add to calendar (org.dgtale.icsimport)
- * - Version 1.2 (3)
- * - Version 1.1 (2)
- */
- private void assertConflictingRepo(List allRepos) {
- Repo repo = findRepo(REPO_CONFLICTING, allRepos);
-
- List apks = ApkProvider.Helper.findByRepo(context, repo, ApkProvider.DataColumns.ALL);
- assertEquals("Apks for main repo", 6, apks.size());
- assertApksExist(apks, "org.adaway", new int[]{50, 51, 52, 53});
- assertApksExist(apks, "org.dgtale.icsimport", new int[]{2, 3});
- }
-
- @NonNull
- private Repo findRepo(@NonNull String name, List allRepos) {
- Repo repo = null;
- for (Repo r : allRepos) {
- if (TextUtils.equals(name, r.getName())) {
- repo = r;
- break;
- }
- }
-
- assertNotNull("Repo " + allRepos, repo);
- return repo;
- }
-
- /**
- * Checks that each version of appId as specified in versionCodes is present in apksToCheck.
- */
- private void assertApksExist(List apksToCheck, String appId, int[] versionCodes) {
- for (int versionCode : versionCodes) {
- boolean found = false;
- for (Apk apk : apksToCheck) {
- if (apk.versionCode == versionCode && apk.packageName.equals(appId)) {
- found = true;
- break;
- }
- }
-
- assertTrue("Couldn't find app " + appId + ", v" + versionCode, found);
- }
- }
-
- private void assertEmpty() {
- assertEquals("No apps present", 0, AppProvider.Helper.all(context.getContentResolver()).size());
-
- String[] packages = {
- "com.uberspot.a2048",
- "org.adaway",
- "siir.es.adbWireless",
- };
-
- for (String id : packages) {
- assertEquals("No apks for " + id, 0, ApkProvider.Helper.findByPackageName(context, id).size());
- }
- }
-
- /* At time fo writing, the following tests did not pass. This is because the multi-repo support
- in F-Droid was not sufficient. When working on proper multi repo support than this should be
- ucommented and all these tests should pass:
-
- public void testCorrectConflictingThenMainThenArchive() throws UpdateException {
- assertEmpty();
- if (updateConflicting() && updateMain() && updateArchive()) {
- assertExpected();
- }
- }
-
- public void testCorrectConflictingThenArchiveThenMain() throws UpdateException {
- assertEmpty();
- if (updateConflicting() && updateArchive() && updateMain()) {
- assertExpected();
- }
- }
-
- public void testCorrectArchiveThenMainThenConflicting() throws UpdateException {
- assertEmpty();
- if (updateArchive() && updateMain() && updateConflicting()) {
- assertExpected();
- }
- }
-
- public void testCorrectArchiveThenConflictingThenMain() throws UpdateException {
- assertEmpty();
- if (updateArchive() && updateConflicting() && updateMain()) {
- assertExpected();
- }
- }
-
- public void testCorrectMainThenArchiveThenConflicting() throws UpdateException {
- assertEmpty();
- if (updateMain() && updateArchive() && updateConflicting()) {
- assertExpected();
- }
- }
-
- public void testCorrectMainThenConflictingThenArchive() throws UpdateException {
- assertEmpty();
- if (updateMain() && updateConflicting() && updateArchive()) {
- assertExpected();
- }
- }
-
- */
-
- public void testAcceptableConflictingThenMainThenArchive() throws UpdateException {
- assertEmpty();
- if (updateConflicting() && updateMain() && updateArchive()) {
- assertSomewhatAcceptable();
- }
- }
-
- public void testAcceptableConflictingThenArchiveThenMain() throws UpdateException {
- assertEmpty();
- if (updateConflicting() && updateArchive() && updateMain()) {
- assertSomewhatAcceptable();
- }
- }
-
- public void testAcceptableArchiveThenMainThenConflicting() throws UpdateException {
- assertEmpty();
- if (updateArchive() && updateMain() && updateConflicting()) {
- assertSomewhatAcceptable();
- }
- }
-
- public void testAcceptableArchiveThenConflictingThenMain() throws UpdateException {
- assertEmpty();
- if (updateArchive() && updateConflicting() && updateMain()) {
- assertSomewhatAcceptable();
- }
- }
-
- public void testAcceptableMainThenArchiveThenConflicting() throws UpdateException {
- assertEmpty();
- if (updateMain() && updateArchive() && updateConflicting()) {
- assertSomewhatAcceptable();
- }
- }
-
- public void testAcceptableMainThenConflictingThenArchive() throws UpdateException {
- assertEmpty();
- if (updateMain() && updateConflicting() && updateArchive()) {
- assertSomewhatAcceptable();
- }
- }
-
- private RepoUpdater createUpdater(String name, Context context) {
- Repo repo = new Repo();
- repo.signingCertificate = PUB_KEY;
- repo.address = "https://fake.url/" + UUID.randomUUID().toString() + "/fdroid/repo";
- repo.name = name;
-
- ContentValues values = new ContentValues(2);
- values.put(RepoProvider.DataColumns.SIGNING_CERT, repo.signingCertificate);
- values.put(RepoProvider.DataColumns.ADDRESS, repo.address);
- values.put(RepoProvider.DataColumns.NAME, repo.name);
-
- RepoProvider.Helper.insert(context, values);
-
- // Need to reload the repo based on address so that it includes the primary key from
- // the database.
- return new RepoUpdater(context, RepoProvider.Helper.findByAddress(context, repo.address));
- }
-
- private boolean updateConflicting() throws UpdateException {
- return updateRepo(conflictingRepoUpdater, "multiRepo.conflicting.jar");
- }
-
- private boolean updateMain() throws UpdateException {
- return updateRepo(mainRepoUpdater, "multiRepo.normal.jar");
- }
-
- private boolean updateArchive() throws UpdateException {
- return updateRepo(archiveRepoUpdater, "multiRepo.archive.jar");
- }
-
- private boolean updateRepo(RepoUpdater updater, String indexJarPath) throws UpdateException {
- if (!testFilesDir.canWrite()) {
- return false;
- }
-
- File indexJar = TestUtils.copyAssetToDir(context, indexJarPath, testFilesDir);
- updater.processDownloadedFile(indexJar);
- return true;
- }
-
-}
diff --git a/app/src/androidTest/java/org/fdroid/fdroid/RepoUpdaterTest.java b/app/src/androidTest/java/org/fdroid/fdroid/RepoUpdaterTest.java
deleted file mode 100644
index ac77cbd60..000000000
--- a/app/src/androidTest/java/org/fdroid/fdroid/RepoUpdaterTest.java
+++ /dev/null
@@ -1,175 +0,0 @@
-
-package org.fdroid.fdroid;
-
-import android.app.Instrumentation;
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-import android.util.Log;
-
-import org.fdroid.fdroid.RepoUpdater.UpdateException;
-import org.fdroid.fdroid.data.Repo;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-@RunWith(AndroidJUnit4.class)
-public class RepoUpdaterTest {
- public static final String TAG = "RepoUpdaterTest";
-
- private Context context;
- private Repo repo;
- private RepoUpdater repoUpdater;
- private File testFilesDir;
-
- String simpleIndexSigningCert = "308201ee30820157a0030201020204300d845b300d06092a864886f70d01010b0500302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e301e170d3134303432373030303633315a170d3431303931323030303633315a302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e30819f300d06092a864886f70d010101050003818d0030818902818100a439472e4b6d01141bfc94ecfe131c7c728fdda670bb14c57ca60bd1c38a8b8bc0879d22a0a2d0bc0d6fdd4cb98d1d607c2caefbe250a0bd0322aedeb365caf9b236992fac13e6675d3184a6c7c6f07f73410209e399a9da8d5d7512bbd870508eebacff8b57c3852457419434d34701ccbf692267cbc3f42f1c5d1e23762d790203010001a321301f301d0603551d0e041604140b1840691dab909746fde4bfe28207d1cae15786300d06092a864886f70d01010b05000381810062424c928ffd1b6fd419b44daafef01ca982e09341f7077fb865905087aeac882534b3bd679b51fdfb98892cef38b63131c567ed26c9d5d9163afc775ac98ad88c405d211d6187bde0b0d236381cc574ba06ef9080721a92ae5a103a7301b2c397eecc141cc850dd3e123813ebc41c59d31ddbcb6e984168280c53272f6a442b";
-
- /**
- * Getting a writeable dir during the tests seems to be a flaky prospect.
- */
- private boolean canWrite() {
- if (testFilesDir.canWrite()) {
- return true;
- } else {
- Log.e(TAG, "ERROR: " + testFilesDir + " is not writable, skipping test");
- return false;
- }
- }
-
- @Before
- public void setUp() {
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- context = instrumentation.getContext();
- testFilesDir = TestUtils.getWriteableDir(instrumentation);
- repo = new Repo();
- repo.address = "https://fake.url/fdroid/repo";
- repo.signingCertificate = this.simpleIndexSigningCert;
- }
-
- @Test
- public void testExtractIndexFromJar() {
- assumeTrue(canWrite());
- File simpleIndexJar = TestUtils.copyAssetToDir(context, "simpleIndex.jar", testFilesDir);
- repoUpdater = new RepoUpdater(context, repo);
-
- // these are supposed to succeed
- try {
- repoUpdater.processDownloadedFile(simpleIndexJar);
- } catch (UpdateException e) {
- e.printStackTrace();
- fail();
- }
- }
-
- @Test(expected = UpdateException.class)
- public void testExtractIndexFromOutdatedJar() throws UpdateException {
- assumeTrue(canWrite());
- File simpleIndexJar = TestUtils.copyAssetToDir(context, "simpleIndex.jar", testFilesDir);
- repo.version = 10;
- repo.timestamp = System.currentTimeMillis() / 1000L;
- repoUpdater = new RepoUpdater(context, repo);
-
- // these are supposed to fail
- repoUpdater.processDownloadedFile(simpleIndexJar);
- fail();
- }
-
- @Test(expected = UpdateException.class)
- public void testExtractIndexFromJarWithoutSignatureJar() throws UpdateException {
- assumeTrue(canWrite());
- // this is supposed to fail
- File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithoutSignature.jar", testFilesDir);
- repoUpdater = new RepoUpdater(context, repo);
- repoUpdater.processDownloadedFile(jarFile);
- fail();
- }
-
- @Test
- public void testExtractIndexFromJarWithCorruptedManifestJar() {
- assumeTrue(canWrite());
- // this is supposed to fail
- try {
- File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithCorruptedManifest.jar", testFilesDir);
- repoUpdater = new RepoUpdater(context, repo);
- repoUpdater.processDownloadedFile(jarFile);
- fail();
- } catch (UpdateException e) {
- e.printStackTrace();
- fail();
- } catch (SecurityException e) {
- // success!
- }
- }
-
- @Test
- public void testExtractIndexFromJarWithCorruptedSignature() {
- assumeTrue(canWrite());
- // this is supposed to fail
- try {
- File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithCorruptedSignature.jar", testFilesDir);
- repoUpdater = new RepoUpdater(context, repo);
- repoUpdater.processDownloadedFile(jarFile);
- fail();
- } catch (UpdateException e) {
- e.printStackTrace();
- fail();
- } catch (SecurityException e) {
- // success!
- }
- }
-
- @Test
- public void testExtractIndexFromJarWithCorruptedCertificate() {
- assumeTrue(canWrite());
- // this is supposed to fail
- try {
- File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithCorruptedCertificate.jar", testFilesDir);
- repoUpdater = new RepoUpdater(context, repo);
- repoUpdater.processDownloadedFile(jarFile);
- fail();
- } catch (UpdateException e) {
- e.printStackTrace();
- fail();
- } catch (SecurityException e) {
- // success!
- }
- }
-
- @Test
- public void testExtractIndexFromJarWithCorruptedEverything() {
- assumeTrue(canWrite());
- // this is supposed to fail
- try {
- File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithCorruptedEverything.jar", testFilesDir);
- repoUpdater = new RepoUpdater(context, repo);
- repoUpdater.processDownloadedFile(jarFile);
- fail();
- } catch (UpdateException e) {
- e.printStackTrace();
- fail();
- } catch (SecurityException e) {
- // success!
- }
- }
-
- @Test
- public void testExtractIndexFromMasterKeyIndexJar() {
- assumeTrue(canWrite());
- // this is supposed to fail
- try {
- File jarFile = TestUtils.copyAssetToDir(context, "masterKeyIndex.jar", testFilesDir);
- repoUpdater = new RepoUpdater(context, repo);
- repoUpdater.processDownloadedFile(jarFile);
- fail(); //NOPMD
- } catch (UpdateException e) {
- // success!
- } catch (SecurityException e) {
- // success!
- }
- }
-}
diff --git a/app/src/androidTest/java/org/fdroid/fdroid/TestUtils.java b/app/src/androidTest/java/org/fdroid/fdroid/TestUtils.java
deleted file mode 100644
index 8b01eb630..000000000
--- a/app/src/androidTest/java/org/fdroid/fdroid/TestUtils.java
+++ /dev/null
@@ -1,196 +0,0 @@
-package org.fdroid.fdroid;
-
-import android.app.Instrumentation;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Environment;
-import android.support.annotation.Nullable;
-import android.util.Log;
-
-import junit.framework.AssertionFailedError;
-
-import org.fdroid.fdroid.data.ApkProvider;
-import org.fdroid.fdroid.data.AppProvider;
-import org.fdroid.fdroid.data.FDroidProviderTest;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-public class TestUtils {
-
- private static final String TAG = "TestUtils";
-
- public static void assertContainsOnly(List actualList, T[] expectedArray) {
- List expectedList = new ArrayList<>(expectedArray.length);
- Collections.addAll(expectedList, expectedArray);
- assertContainsOnly(actualList, expectedList);
- }
-
- public static void assertContainsOnly(T[] actualArray, List expectedList) {
- List actualList = new ArrayList<>(actualArray.length);
- Collections.addAll(actualList, actualArray);
- assertContainsOnly(actualList, expectedList);
- }
-
- public static void assertContainsOnly(T[] actualArray, T[] expectedArray) {
- List expectedList = new ArrayList<>(expectedArray.length);
- Collections.addAll(expectedList, expectedArray);
- assertContainsOnly(actualArray, expectedList);
- }
-
- public static String listToString(List list) {
- String string = "[";
- for (int i = 0; i < list.size(); i++) {
- if (i > 0) {
- string += ", ";
- }
- string += "'" + list.get(i) + "'";
- }
- string += "]";
- return string;
- }
-
- public static void assertContainsOnly(List actualList, List expectedContains) {
- if (actualList.size() != expectedContains.size()) {
- String message =
- "List sizes don't match.\n" +
- "Expected: " +
- listToString(expectedContains) + "\n" +
- "Actual: " +
- listToString(actualList);
- throw new AssertionFailedError(message);
- }
- for (T required : expectedContains) {
- boolean containsRequired = false;
- for (T itemInList : actualList) {
- if (required.equals(itemInList)) {
- containsRequired = true;
- break;
- }
- }
- if (!containsRequired) {
- String message =
- "List doesn't contain \"" + required + "\".\n" +
- "Expected: " +
- listToString(expectedContains) + "\n" +
- "Actual: " +
- listToString(actualList);
- throw new AssertionFailedError(message);
- }
- }
- }
-
- public static void insertApp(ContentResolver resolver, String appId, String name) {
- insertApp(resolver, appId, name, new ContentValues());
- }
-
- public static void insertApp(ContentResolver resolver, String id, String name, ContentValues additionalValues) {
-
- ContentValues values = new ContentValues();
- values.put(AppProvider.DataColumns.PACKAGE_NAME, id);
- values.put(AppProvider.DataColumns.NAME, name);
-
- // Required fields (NOT NULL in the database).
- values.put(AppProvider.DataColumns.SUMMARY, "test summary");
- values.put(AppProvider.DataColumns.DESCRIPTION, "test description");
- values.put(AppProvider.DataColumns.LICENSE, "GPL?");
- values.put(AppProvider.DataColumns.IS_COMPATIBLE, 1);
- values.put(AppProvider.DataColumns.IGNORE_ALLUPDATES, 0);
- values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, 0);
-
- values.putAll(additionalValues);
-
- Uri uri = AppProvider.getContentUri();
-
- resolver.insert(uri, values);
- }
-
- public static Uri insertApk(FDroidProviderTest providerTest, String id, int versionCode) {
- return insertApk(providerTest, id, versionCode, new ContentValues());
- }
-
- public static Uri insertApk(FDroidProviderTest providerTest, String id, int versionCode, ContentValues additionalValues) {
-
- ContentValues values = new ContentValues();
-
- values.put(ApkProvider.DataColumns.PACKAGE_NAME, id);
- values.put(ApkProvider.DataColumns.VERSION_CODE, versionCode);
-
- // Required fields (NOT NULL in the database).
- values.put(ApkProvider.DataColumns.REPO_ID, 1);
- values.put(ApkProvider.DataColumns.VERSION_NAME, "The good one");
- values.put(ApkProvider.DataColumns.HASH, "11111111aaaaaaaa");
- values.put(ApkProvider.DataColumns.NAME, "Test Apk");
- values.put(ApkProvider.DataColumns.SIZE, 10000);
- values.put(ApkProvider.DataColumns.IS_COMPATIBLE, 1);
-
- values.putAll(additionalValues);
-
- Uri uri = ApkProvider.getContentUri();
-
- return providerTest.getMockContentResolver().insert(uri, values);
- }
-
- @Nullable
- public static File copyAssetToDir(Context context, String assetName, File directory) {
- File tempFile;
- InputStream input = null;
- OutputStream output = null;
- try {
- tempFile = File.createTempFile(assetName + "-", ".testasset", directory);
- Log.i(TAG, "Copying asset file " + assetName + " to directory " + directory);
- input = context.getAssets().open(assetName);
- output = new FileOutputStream(tempFile);
- Utils.copy(input, output);
- } catch (IOException e) {
- e.printStackTrace();
- return null;
- } finally {
- Utils.closeQuietly(output);
- Utils.closeQuietly(input);
- }
- return tempFile;
- }
-
- /**
- * Prefer internal over external storage, because external tends to be FAT filesystems,
- * which don't support symlinks (which we test using this method).
- */
- public static File getWriteableDir(Instrumentation instrumentation) {
- Context context = instrumentation.getContext();
- Context targetContext = instrumentation.getTargetContext();
-
- File[] dirsToTry = new File[]{
- context.getCacheDir(),
- context.getFilesDir(),
- targetContext.getCacheDir(),
- targetContext.getFilesDir(),
- context.getExternalCacheDir(),
- context.getExternalFilesDir(null),
- targetContext.getExternalCacheDir(),
- targetContext.getExternalFilesDir(null),
- Environment.getExternalStorageDirectory(),
- };
-
- return getWriteableDir(dirsToTry);
- }
-
- private static File getWriteableDir(File[] dirsToTry) {
-
- for (File dir : dirsToTry) {
- if (dir != null && dir.canWrite()) {
- return dir;
- }
- }
-
- return null;
- }
-}
diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/ApkProviderHelperTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/ApkProviderHelperTest.java
deleted file mode 100644
index 307655a72..000000000
--- a/app/src/androidTest/java/org/fdroid/fdroid/data/ApkProviderHelperTest.java
+++ /dev/null
@@ -1,216 +0,0 @@
-package org.fdroid.fdroid.data;
-
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-
-import org.fdroid.fdroid.TestUtils;
-import org.fdroid.fdroid.Utils;
-import org.fdroid.fdroid.mock.MockApk;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-
-@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
-public class ApkProviderHelperTest extends BaseApkProviderTest {
-
- public void testKnownApks() {
-
- for (int i = 0; i < 7; i++) {
- TestUtils.insertApk(this, "org.fdroid.fdroid", i);
- }
-
- for (int i = 0; i < 9; i++) {
- TestUtils.insertApk(this, "org.example", i);
- }
-
- for (int i = 0; i < 3; i++) {
- TestUtils.insertApk(this, "com.example", i);
- }
-
- TestUtils.insertApk(this, "com.apk.thingo", 1);
-
- Apk[] known = {
- new MockApk("org.fdroid.fdroid", 1),
- new MockApk("org.fdroid.fdroid", 3),
- new MockApk("org.fdroid.fdroid", 5),
-
- new MockApk("com.example", 1),
- new MockApk("com.example", 2),
- };
-
- Apk[] unknown = {
- new MockApk("org.fdroid.fdroid", 7),
- new MockApk("org.fdroid.fdroid", 9),
- new MockApk("org.fdroid.fdroid", 11),
- new MockApk("org.fdroid.fdroid", 13),
-
- new MockApk("com.example", 3),
- new MockApk("com.example", 4),
- new MockApk("com.example", 5),
-
- new MockApk("info.example", 1),
- new MockApk("info.example", 2),
- };
-
- List apksToCheck = new ArrayList<>(known.length + unknown.length);
- Collections.addAll(apksToCheck, known);
- Collections.addAll(apksToCheck, unknown);
-
- String[] projection = {
- ApkProvider.DataColumns.PACKAGE_NAME,
- ApkProvider.DataColumns.VERSION_CODE,
- };
-
- List knownApks = ApkProvider.Helper.knownApks(getMockContext(), apksToCheck, projection);
-
- assertResultCount(known.length, knownApks);
-
- for (Apk knownApk : knownApks) {
- assertContains(knownApks, knownApk);
- }
- }
-
- public void testFindByApp() {
-
- for (int i = 0; i < 7; i++) {
- TestUtils.insertApk(this, "org.fdroid.fdroid", i);
- }
-
- for (int i = 0; i < 9; i++) {
- TestUtils.insertApk(this, "org.example", i);
- }
-
- for (int i = 0; i < 3; i++) {
- TestUtils.insertApk(this, "com.example", i);
- }
-
- TestUtils.insertApk(this, "com.apk.thingo", 1);
-
- assertTotalApkCount(7 + 9 + 3 + 1);
-
- List fdroidApks = ApkProvider.Helper.findByPackageName(getMockContext(), "org.fdroid.fdroid");
- assertResultCount(7, fdroidApks);
- assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
-
- List exampleApks = ApkProvider.Helper.findByPackageName(getMockContext(), "org.example");
- assertResultCount(9, exampleApks);
- assertBelongsToApp(exampleApks, "org.example");
-
- List exampleApks2 = ApkProvider.Helper.findByPackageName(getMockContext(), "com.example");
- assertResultCount(3, exampleApks2);
- assertBelongsToApp(exampleApks2, "com.example");
-
- List thingoApks = ApkProvider.Helper.findByPackageName(getMockContext(), "com.apk.thingo");
- assertResultCount(1, thingoApks);
- assertBelongsToApp(thingoApks, "com.apk.thingo");
- }
-
- public void testUpdate() {
-
- Uri apkUri = TestUtils.insertApk(this, "com.example", 10);
-
- String[] allFields = ApkProvider.DataColumns.ALL;
- Cursor cursor = getMockContentResolver().query(apkUri, allFields, null, null, null);
- assertResultCount(1, cursor);
-
- cursor.moveToFirst();
- Apk apk = new Apk(cursor);
- cursor.close();
-
- assertEquals("com.example", apk.packageName);
- assertEquals(10, apk.versionCode);
-
- assertNull(apk.features);
- assertNull(apk.added);
- assertNull(apk.hashType);
-
- apk.features = Utils.CommaSeparatedList.make("one,two,three");
- long dateTimestamp = System.currentTimeMillis();
- apk.added = new Date(dateTimestamp);
- apk.hashType = "i'm a hash type";
-
- ApkProvider.Helper.update(getMockContext(), apk);
-
- // Should not have inserted anything else, just updated the already existing apk.
- Cursor allCursor = getMockContentResolver().query(ApkProvider.getContentUri(), allFields, null, null, null);
- assertResultCount(1, allCursor);
- allCursor.close();
-
- Cursor updatedCursor = getMockContentResolver().query(apkUri, allFields, null, null, null);
- assertResultCount(1, updatedCursor);
-
- updatedCursor.moveToFirst();
- Apk updatedApk = new Apk(updatedCursor);
- updatedCursor.close();
-
- assertEquals("com.example", updatedApk.packageName);
- assertEquals(10, updatedApk.versionCode);
-
- assertNotNull(updatedApk.features);
- assertNotNull(updatedApk.added);
- assertNotNull(updatedApk.hashType);
-
- assertEquals("one,two,three", updatedApk.features.toString());
- assertEquals(new Date(dateTimestamp).getYear(), updatedApk.added.getYear());
- assertEquals(new Date(dateTimestamp).getMonth(), updatedApk.added.getMonth());
- assertEquals(new Date(dateTimestamp).getDay(), updatedApk.added.getDay());
- assertEquals("i'm a hash type", updatedApk.hashType);
- }
-
- public void testFind() {
-
- // Insert some random apks either side of the "com.example", so that
- // the Helper.find() method doesn't stumble upon the app we are interested
- // in by shear dumb luck...
- for (int i = 0; i < 10; i++) {
- TestUtils.insertApk(this, "org.fdroid.apk." + i, i);
- }
-
- ContentValues values = new ContentValues();
- values.put(ApkProvider.DataColumns.VERSION_NAME, "v1.1");
- values.put(ApkProvider.DataColumns.HASH, "xxxxyyyy");
- values.put(ApkProvider.DataColumns.HASH_TYPE, "a hash type");
- TestUtils.insertApk(this, "com.example", 11, values);
-
- // ...and a few more for good measure...
- for (int i = 15; i < 20; i++) {
- TestUtils.insertApk(this, "com.other.thing." + i, i);
- }
-
- Apk apk = ApkProvider.Helper.find(getMockContext(), "com.example", 11);
-
- assertNotNull(apk);
-
- // The find() method populates ALL fields if you don't specify any,
- // so we expect to find each of the ones we inserted above...
- assertEquals("com.example", apk.packageName);
- assertEquals(11, apk.versionCode);
- assertEquals("v1.1", apk.versionName);
- assertEquals("xxxxyyyy", apk.hash);
- assertEquals("a hash type", apk.hashType);
-
- String[] projection = {
- ApkProvider.DataColumns.PACKAGE_NAME,
- ApkProvider.DataColumns.HASH,
- };
-
- Apk apkLessFields = ApkProvider.Helper.find(getMockContext(), "com.example", 11, projection);
-
- assertNotNull(apkLessFields);
-
- assertEquals("com.example", apkLessFields.packageName);
- assertEquals("xxxxyyyy", apkLessFields.hash);
-
- // Didn't ask for these fields, so should be their default values...
- assertNull(apkLessFields.hashType);
- assertNull(apkLessFields.versionName);
- assertEquals(0, apkLessFields.versionCode);
-
- Apk notFound = ApkProvider.Helper.find(getMockContext(), "com.doesnt.exist", 1000);
- assertNull(notFound);
- }
-
-}
diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/ApkProviderTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/ApkProviderTest.java
deleted file mode 100644
index 4dced204f..000000000
--- a/app/src/androidTest/java/org/fdroid/fdroid/data/ApkProviderTest.java
+++ /dev/null
@@ -1,334 +0,0 @@
-package org.fdroid.fdroid.data;
-
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-
-import org.fdroid.fdroid.TestUtils;
-import org.fdroid.fdroid.mock.MockApk;
-import org.fdroid.fdroid.mock.MockApp;
-import org.fdroid.fdroid.mock.MockRepo;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
-public class ApkProviderTest extends BaseApkProviderTest {
-
- /**
- * I want to test the protected {@link org.fdroid.fdroid.data.ApkProvider#getContentUri(java.util.List)}
- * method, but don't want to make it public. This exposes it.
- */
- private static class PublicApkProvider extends ApkProvider {
-
- public static final int MAX_APKS_TO_QUERY = ApkProvider.MAX_APKS_TO_QUERY;
-
- public static Uri getContentUri(List apks) {
- return ApkProvider.getContentUri(apks);
- }
- }
-
- public void testUris() {
- assertInvalidUri(ApkProvider.getAuthority());
- assertInvalidUri(RepoProvider.getContentUri());
-
- List apks = new ArrayList<>(3);
- for (int i = 0; i < 10; i++) {
- apks.add(new MockApk("com.example." + i, i));
- }
-
- assertValidUri(ApkProvider.getContentUri());
- assertValidUri(ApkProvider.getAppUri("org.fdroid.fdroid"));
- assertValidUri(ApkProvider.getContentUri(new MockApk("org.fdroid.fdroid", 100)));
- assertValidUri(ApkProvider.getContentUri());
- assertValidUri(PublicApkProvider.getContentUri(apks));
- assertValidUri(ApkProvider.getContentUri("org.fdroid.fdroid", 100));
- assertValidUri(ApkProvider.getRepoUri(1000));
-
- List manyApks = new ArrayList<>(PublicApkProvider.MAX_APKS_TO_QUERY - 5);
- for (int i = 0; i < PublicApkProvider.MAX_APKS_TO_QUERY - 1; i++) {
- manyApks.add(new MockApk("com.example." + i, i));
- }
- assertValidUri(PublicApkProvider.getContentUri(manyApks));
-
- manyApks.add(new MockApk("org.fdroid.fdroid.1", 1));
- manyApks.add(new MockApk("org.fdroid.fdroid.2", 2));
- try {
- // Technically, it is a valid URI, because it doesn't
- // throw an UnsupportedOperationException. However it
- // is still not okay (we run out of bindable parameters
- // in the sqlite query.
- assertValidUri(PublicApkProvider.getContentUri(manyApks));
- fail();
- } catch (IllegalArgumentException e) {
- // This is the expected error behaviour.
- } catch (Exception e) {
- fail();
- }
- }
-
- public void testAppApks() {
- for (int i = 1; i <= 10; i++) {
- TestUtils.insertApk(this, "org.fdroid.fdroid", i);
- TestUtils.insertApk(this, "com.example", i);
- }
-
- assertTotalApkCount(20);
-
- Cursor fdroidApks = getMockContentResolver().query(
- ApkProvider.getAppUri("org.fdroid.fdroid"),
- getMinimalProjection(),
- null, null, null);
- assertResultCount(10, fdroidApks);
- assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
- fdroidApks.close();
-
- Cursor exampleApks = getMockContentResolver().query(
- ApkProvider.getAppUri("com.example"),
- getMinimalProjection(),
- null, null, null);
- assertResultCount(10, exampleApks);
- assertBelongsToApp(exampleApks, "com.example");
- exampleApks.close();
-
- ApkProvider.Helper.deleteApksByApp(getMockContext(), new MockApp("com.example"));
-
- Cursor all = queryAllApks();
- assertResultCount(10, all);
- assertBelongsToApp(all, "org.fdroid.fdroid");
- all.close();
- }
-
- public void testInvalidUpdateUris() {
- Apk apk = new MockApk("org.fdroid.fdroid", 10);
-
- List apks = new ArrayList<>();
- apks.add(apk);
-
- assertCantUpdate(ApkProvider.getContentUri());
- assertCantUpdate(ApkProvider.getAppUri("org.fdroid.fdroid"));
- assertCantUpdate(ApkProvider.getRepoUri(1));
- assertCantUpdate(PublicApkProvider.getContentUri(apks));
- assertCantUpdate(Uri.withAppendedPath(ApkProvider.getContentUri(), "some-random-path"));
-
- // The only valid ones are:
- // ApkProvider.getContentUri(apk)
- // ApkProvider.getContentUri(id, version)
- // which are tested elsewhere.
- }
-
- public void testDeleteArbitraryApks() {
- Apk one = insertApkForRepo("com.example.one", 1, 10);
- Apk two = insertApkForRepo("com.example.two", 1, 10);
- Apk three = insertApkForRepo("com.example.three", 1, 10);
- Apk four = insertApkForRepo("com.example.four", 1, 10);
- Apk five = insertApkForRepo("com.example.five", 1, 10);
-
- assertTotalApkCount(5);
-
- assertEquals("com.example.one", one.packageName);
- assertEquals("com.example.two", two.packageName);
- assertEquals("com.example.five", five.packageName);
-
- String[] expectedIds = {
- "com.example.one",
- "com.example.two",
- "com.example.three",
- "com.example.four",
- "com.example.five",
- };
-
- List all = ApkProvider.Helper.findByRepo(getSwappableContext(), new MockRepo(10), ApkProvider.DataColumns.ALL);
- List actualIds = new ArrayList<>();
- for (Apk apk : all) {
- actualIds.add(apk.packageName);
- }
-
- TestUtils.assertContainsOnly(actualIds, expectedIds);
-
- List toDelete = new ArrayList<>(3);
- toDelete.add(two);
- toDelete.add(three);
- toDelete.add(four);
- ApkProvider.Helper.deleteApks(getSwappableContext(), toDelete);
-
- assertTotalApkCount(2);
-
- List allRemaining = ApkProvider.Helper.findByRepo(getSwappableContext(), new MockRepo(10), ApkProvider.DataColumns.ALL);
- List actualRemainingIds = new ArrayList<>();
- for (Apk apk : allRemaining) {
- actualRemainingIds.add(apk.packageName);
- }
-
- String[] expectedRemainingIds = {
- "com.example.one",
- "com.example.five",
- };
-
- TestUtils.assertContainsOnly(actualRemainingIds, expectedRemainingIds);
- }
-
- public void testInvalidDeleteUris() {
- Apk apk = new MockApk("org.fdroid.fdroid", 10);
-
- assertCantDelete(ApkProvider.getContentUri());
- assertCantDelete(ApkProvider.getContentUri("org.fdroid.fdroid", 10));
- assertCantDelete(ApkProvider.getContentUri(apk));
- assertCantDelete(Uri.withAppendedPath(ApkProvider.getContentUri(), "some-random-path"));
- }
-
- private static final long REPO_KEEP = 1;
- private static final long REPO_DELETE = 2;
-
- public void testRepoApks() {
-
- // Insert apks into two repos, one of which we will later purge the
- // the apks from.
- for (int i = 1; i <= 5; i++) {
- insertApkForRepo("org.fdroid.fdroid", i, REPO_KEEP);
- insertApkForRepo("com.example." + i, 1, REPO_DELETE);
- }
- for (int i = 6; i <= 10; i++) {
- insertApkForRepo("org.fdroid.fdroid", i, REPO_DELETE);
- insertApkForRepo("com.example." + i, 1, REPO_KEEP);
- }
-
- assertTotalApkCount(20);
-
- Cursor cursor = getMockContentResolver().query(
- ApkProvider.getRepoUri(REPO_DELETE), getMinimalProjection(), null, null, null);
- assertResultCount(10, cursor);
- assertBelongsToRepo(cursor, REPO_DELETE);
- cursor.close();
-
- int count = ApkProvider.Helper.deleteApksByRepo(getMockContext(), new MockRepo(REPO_DELETE));
- assertEquals(10, count);
-
- assertTotalApkCount(10);
- cursor = getMockContentResolver().query(
- ApkProvider.getRepoUri(REPO_DELETE), getMinimalProjection(), null, null, null);
- assertResultCount(0, cursor);
- cursor.close();
-
- // The only remaining apks should be those from REPO_KEEP.
- assertBelongsToRepo(queryAllApks(), REPO_KEEP);
- }
-
- public void testQuery() {
- Cursor cursor = queryAllApks();
- assertNotNull(cursor);
- cursor.close();
- }
-
- public void testInsert() {
-
- // Start with an empty database...
- Cursor cursor = queryAllApks();
- assertNotNull(cursor);
- assertEquals(0, cursor.getCount());
- cursor.close();
-
- Apk apk = new MockApk("org.fdroid.fdroid", 13);
-
- // Insert a new record...
- Uri newUri = TestUtils.insertApk(this, apk.packageName, apk.versionCode);
- assertEquals(ApkProvider.getContentUri(apk).toString(), newUri.toString());
- cursor = queryAllApks();
- assertNotNull(cursor);
- assertEquals(1, cursor.getCount());
-
- // We intentionally throw an IllegalArgumentException if you haven't
- // yet called cursor.move*()...
- try {
- new Apk(cursor);
- fail();
- } catch (IllegalArgumentException e) {
- // Success!
- } catch (Exception e) {
- fail();
- }
-
- // And now we should be able to recover these values from the apk
- // value object (because the queryAllApks() helper asks for VERSION_CODE and
- // PACKAGE_NAME.
- cursor.moveToFirst();
- Apk toCheck = new Apk(cursor);
- cursor.close();
- assertEquals("org.fdroid.fdroid", toCheck.packageName);
- assertEquals(13, toCheck.versionCode);
- }
-
- public void testCount() {
- String[] projectionFields = getMinimalProjection();
- String[] projectionCount = new String[] {ApkProvider.DataColumns._COUNT};
-
- for (int i = 0; i < 13; i++) {
- TestUtils.insertApk(this, "com.example", i);
- }
-
- Uri all = ApkProvider.getContentUri();
- Cursor allWithFields = getMockContentResolver().query(all, projectionFields, null, null, null);
- Cursor allWithCount = getMockContentResolver().query(all, projectionCount, null, null, null);
-
- assertResultCount(13, allWithFields);
- allWithFields.close();
- assertResultCount(1, allWithCount);
-
- allWithCount.moveToFirst();
- int countColumn = allWithCount.getColumnIndex(ApkProvider.DataColumns._COUNT);
- assertEquals(13, allWithCount.getInt(countColumn));
- allWithCount.close();
- }
-
- public void testInsertWithExtraFields() {
-
- assertResultCount(0, queryAllApks());
-
- String[] repoFields = new String[] {
- RepoProvider.DataColumns.DESCRIPTION,
- RepoProvider.DataColumns.ADDRESS,
- RepoProvider.DataColumns.FINGERPRINT,
- RepoProvider.DataColumns.NAME,
- RepoProvider.DataColumns.SIGNING_CERT,
- };
-
- for (String field : repoFields) {
- ContentValues invalidRepo = new ContentValues();
- invalidRepo.put(field, "Test data");
- try {
- TestUtils.insertApk(this, "org.fdroid.fdroid", 10, invalidRepo);
- fail();
- } catch (IllegalArgumentException e) {
- } catch (Exception e) {
- fail();
- }
- assertResultCount(0, queryAllApks());
- }
-
- ContentValues values = new ContentValues();
- values.put(ApkProvider.DataColumns.REPO_ID, 10);
- values.put(ApkProvider.DataColumns.REPO_ADDRESS, "http://example.com");
- values.put(ApkProvider.DataColumns.REPO_VERSION, 3);
- values.put(ApkProvider.DataColumns.FEATURES, "Some features");
- Uri uri = TestUtils.insertApk(this, "com.example.com", 1, values);
-
- assertResultCount(1, queryAllApks());
-
- String[] projections = ApkProvider.DataColumns.ALL;
- Cursor cursor = getMockContentResolver().query(uri, projections, null, null, null);
- cursor.moveToFirst();
- Apk apk = new Apk(cursor);
- cursor.close();
-
- // These should have quietly been dropped when we tried to save them,
- // because the provider only knows how to query them (not update them).
- assertEquals(null, apk.repoAddress);
- assertEquals(0, apk.repoVersion);
-
- // But this should have saved correctly...
- assertEquals("Some features", apk.features.toString());
- assertEquals("com.example.com", apk.packageName);
- assertEquals(1, apk.versionCode);
- assertEquals(10, apk.repo);
- }
-}
diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/AppProviderTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/AppProviderTest.java
deleted file mode 100644
index 454759c0e..000000000
--- a/app/src/androidTest/java/org/fdroid/fdroid/data/AppProviderTest.java
+++ /dev/null
@@ -1,389 +0,0 @@
-package org.fdroid.fdroid.data;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.pm.PackageInfo;
-import android.content.res.Resources;
-import android.database.Cursor;
-
-import org.fdroid.fdroid.R;
-import org.fdroid.fdroid.TestUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import mock.MockCategoryResources;
-import mock.MockContextSwappableComponents;
-import mock.MockInstallablePackageManager;
-
-@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
-public class AppProviderTest extends FDroidProviderTest {
-
- public AppProviderTest() {
- super(AppProvider.class, AppProvider.getAuthority());
- }
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- getSwappableContext().setResources(new MockCategoryResources(getContext()));
- }
-
- @Override
- protected Resources getMockResources() {
- return new MockCategoryResources(getContext());
- }
-
- @Override
- protected String[] getMinimalProjection() {
- return new String[] {
- AppProvider.DataColumns.PACKAGE_NAME,
- AppProvider.DataColumns.NAME,
- };
- }
-
- /**
- * Although this doesn't directly relate to the AppProvider, it is here because
- * the AppProvider used to stumble across this bug when asking for installed apps,
- * and the device had over 1000 apps installed.
- */
- public void testMaxSqliteParams() {
-
- MockInstallablePackageManager pm = new MockInstallablePackageManager();
- getSwappableContext().setPackageManager(pm);
-
- insertApp("com.example.app1", "App 1");
- insertApp("com.example.app100", "App 100");
- insertApp("com.example.app1000", "App 1000");
-
- for (int i = 0; i < 50; i++) {
- String packageName = "com.example.app" + i;
- pm.install(packageName, 1, "v" + 1);
- PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
- InstalledAppProviderService.insertAppIntoDb(getSwappableContext(), packageName, packageInfo);
- }
- assertResultCount(1, AppProvider.getInstalledUri());
-
- for (int i = 50; i < 500; i++) {
- String packageName = "com.example.app" + i;
- pm.install(packageName, 1, "v" + 1);
- PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
- InstalledAppProviderService.insertAppIntoDb(getSwappableContext(), packageName, packageInfo);
- }
- assertResultCount(2, AppProvider.getInstalledUri());
-
- for (int i = 500; i < 1100; i++) {
- String packageName = "com.example.app" + i;
- pm.install(packageName, 1, "v" + 1);
- PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
- InstalledAppProviderService.insertAppIntoDb(getSwappableContext(), packageName, packageInfo);
- }
- assertResultCount(3, AppProvider.getInstalledUri());
- }
-
- public void testCantFindApp() {
- assertNull(AppProvider.Helper.findByPackageName(getMockContentResolver(), "com.example.doesnt-exist"));
- }
-
- public void testUris() {
- assertInvalidUri(AppProvider.getAuthority());
- assertInvalidUri(ApkProvider.getContentUri());
-
- assertValidUri(AppProvider.getContentUri(), "content://org.fdroid.fdroid.data.AppProvider");
- assertValidUri(AppProvider.getSearchUri("'searching!'"), "content://org.fdroid.fdroid.data.AppProvider/search/'searching!'");
- assertValidUri(AppProvider.getSearchUri("/"), "content://org.fdroid.fdroid.data.AppProvider/search/%2F");
- assertValidUri(AppProvider.getSearchUri(""), "content://org.fdroid.fdroid.data.AppProvider");
- assertValidUri(AppProvider.getSearchUri(null), "content://org.fdroid.fdroid.data.AppProvider");
- assertValidUri(AppProvider.getNoApksUri());
- assertValidUri(AppProvider.getInstalledUri());
- assertValidUri(AppProvider.getCanUpdateUri());
-
- App app = new App();
- app.packageName = "org.fdroid.fdroid";
-
- List apps = new ArrayList<>(1);
- apps.add(app);
-
- assertValidUri(AppProvider.getContentUri(app));
- assertValidUri(AppProvider.getContentUri(apps));
- assertValidUri(AppProvider.getContentUri("org.fdroid.fdroid"));
- }
-
- public void testQuery() {
- Cursor cursor = queryAllApps();
- assertNotNull(cursor);
- cursor.close();
- }
-
- private void insertApps(int count) {
- for (int i = 0; i < count; i++) {
- insertApp("com.example.test." + i, "Test app " + i);
- }
- }
-
- private void insertAndInstallApp(
- MockInstallablePackageManager packageManager,
- String id, int installedVercode, int suggestedVercode,
- boolean ignoreAll, int ignoreVercode) {
- ContentValues values = new ContentValues(3);
- values.put(AppProvider.DataColumns.SUGGESTED_VERSION_CODE, suggestedVercode);
- values.put(AppProvider.DataColumns.IGNORE_ALLUPDATES, ignoreAll);
- values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, ignoreVercode);
- insertApp(id, "App: " + id, values);
-
- InstalledAppProviderTest.install(getSwappableContext(), packageManager, id, installedVercode, "v" + installedVercode);
- }
-
- public void testCanUpdate() {
-
- MockContextSwappableComponents c = getSwappableContext();
-
- MockInstallablePackageManager pm = new MockInstallablePackageManager();
- c.setPackageManager(pm);
-
- insertApp("not installed", "not installed");
- insertAndInstallApp(pm, "installed, only one version available", 1, 1, false, 0);
- insertAndInstallApp(pm, "installed, already latest, no ignore", 10, 10, false, 0);
- insertAndInstallApp(pm, "installed, already latest, ignore all", 10, 10, true, 0);
- insertAndInstallApp(pm, "installed, already latest, ignore latest", 10, 10, false, 10);
- insertAndInstallApp(pm, "installed, already latest, ignore old", 10, 10, false, 5);
- insertAndInstallApp(pm, "installed, old version, no ignore", 5, 10, false, 0);
- insertAndInstallApp(pm, "installed, old version, ignore all", 5, 10, true, 0);
- insertAndInstallApp(pm, "installed, old version, ignore latest", 5, 10, false, 10);
- insertAndInstallApp(pm, "installed, old version, ignore newer, but not latest", 5, 10, false, 8);
-
- ContentResolver r = getMockContentResolver();
-
- // Can't "update", although can "install"...
- App notInstalled = AppProvider.Helper.findByPackageName(r, "not installed");
- assertFalse(notInstalled.canAndWantToUpdate());
-
- App installedOnlyOneVersionAvailable = AppProvider.Helper.findByPackageName(r, "installed, only one version available");
- App installedAlreadyLatestNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, already latest, no ignore");
- App installedAlreadyLatestIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore all");
- App installedAlreadyLatestIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore latest");
- App installedAlreadyLatestIgnoreOld = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore old");
-
- assertFalse(installedOnlyOneVersionAvailable.canAndWantToUpdate());
- assertFalse(installedAlreadyLatestNoIgnore.canAndWantToUpdate());
- assertFalse(installedAlreadyLatestIgnoreAll.canAndWantToUpdate());
- assertFalse(installedAlreadyLatestIgnoreLatest.canAndWantToUpdate());
- assertFalse(installedAlreadyLatestIgnoreOld.canAndWantToUpdate());
-
- App installedOldNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, old version, no ignore");
- App installedOldIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore all");
- App installedOldIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore latest");
- App installedOldIgnoreNewerNotLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore newer, but not latest");
-
- assertTrue(installedOldNoIgnore.canAndWantToUpdate());
- assertFalse(installedOldIgnoreAll.canAndWantToUpdate());
- assertFalse(installedOldIgnoreLatest.canAndWantToUpdate());
- assertTrue(installedOldIgnoreNewerNotLatest.canAndWantToUpdate());
-
- Cursor canUpdateCursor = r.query(AppProvider.getCanUpdateUri(), AppProvider.DataColumns.ALL, null, null, null);
- canUpdateCursor.moveToFirst();
- List canUpdateIds = new ArrayList<>(canUpdateCursor.getCount());
- while (!canUpdateCursor.isAfterLast()) {
- canUpdateIds.add(new App(canUpdateCursor).packageName);
- canUpdateCursor.moveToNext();
- }
- canUpdateCursor.close();
-
- String[] expectedUpdateableIds = {
- "installed, old version, no ignore",
- "installed, old version, ignore newer, but not latest",
- };
-
- TestUtils.assertContainsOnly(expectedUpdateableIds, canUpdateIds);
- }
-
- public void testIgnored() {
-
- MockInstallablePackageManager pm = new MockInstallablePackageManager();
- getSwappableContext().setPackageManager(pm);
-
- insertApp("not installed", "not installed");
- insertAndInstallApp(pm, "installed, only one version available", 1, 1, false, 0);
- insertAndInstallApp(pm, "installed, already latest, no ignore", 10, 10, false, 0);
- insertAndInstallApp(pm, "installed, already latest, ignore all", 10, 10, true, 0);
- insertAndInstallApp(pm, "installed, already latest, ignore latest", 10, 10, false, 10);
- insertAndInstallApp(pm, "installed, already latest, ignore old", 10, 10, false, 5);
- insertAndInstallApp(pm, "installed, old version, no ignore", 5, 10, false, 0);
- insertAndInstallApp(pm, "installed, old version, ignore all", 5, 10, true, 0);
- insertAndInstallApp(pm, "installed, old version, ignore latest", 5, 10, false, 10);
- insertAndInstallApp(pm, "installed, old version, ignore newer, but not latest", 5, 10, false, 8);
-
- assertResultCount(10, AppProvider.getContentUri());
-
- String[] projection = {AppProvider.DataColumns.PACKAGE_NAME};
- List ignoredApps = AppProvider.Helper.findIgnored(getMockContext(), projection);
-
- String[] expectedIgnored = {
- "installed, already latest, ignore all",
- "installed, already latest, ignore latest",
- // NOT "installed, already latest, ignore old" - because it
- // is should only ignore if "ignored version" is >= suggested
-
- "installed, old version, ignore all",
- "installed, old version, ignore latest",
- // NOT "installed, old version, ignore newer, but not latest"
- // for the same reason as above.
- };
-
- assertContainsOnlyIds(ignoredApps, expectedIgnored);
- }
-
- private void assertContainsOnlyIds(List actualApps, String[] expectedIds) {
- List actualIds = new ArrayList<>(actualApps.size());
- for (App app : actualApps) {
- actualIds.add(app.packageName);
- }
- TestUtils.assertContainsOnly(actualIds, expectedIds);
- }
-
- public void testInstalled() {
- MockInstallablePackageManager pm = new MockInstallablePackageManager();
- getSwappableContext().setPackageManager(pm);
-
- insertApps(100);
-
- assertResultCount(100, AppProvider.getContentUri());
- assertResultCount(0, AppProvider.getInstalledUri());
-
- for (int i = 10; i < 20; i++) {
- InstalledAppProviderTest.install(getSwappableContext(), pm, "com.example.test." + i, i, "v1");
- }
-
- assertResultCount(10, AppProvider.getInstalledUri());
- }
-
- public void testInsert() {
-
- // Start with an empty database...
- Cursor cursor = queryAllApps();
- assertNotNull(cursor);
- assertEquals(0, cursor.getCount());
- cursor.close();
-
- // Insert a new record...
- insertApp("org.fdroid.fdroid", "F-Droid");
- cursor = queryAllApps();
- assertNotNull(cursor);
- assertEquals(1, cursor.getCount());
-
- // We intentionally throw an IllegalArgumentException if you haven't
- // yet called cursor.move*()...
- try {
- new App(cursor);
- fail();
- } catch (IllegalArgumentException e) {
- // Success!
- } catch (Exception e) {
- fail();
- }
-
- // And now we should be able to recover these values from the app
- // value object (because the queryAllApps() helper asks for NAME and
- // PACKAGE_NAME.
- cursor.moveToFirst();
- App app = new App(cursor);
- cursor.close();
- assertEquals("org.fdroid.fdroid", app.packageName);
- assertEquals("F-Droid", app.name);
- }
-
- private Cursor queryAllApps() {
- return getMockContentResolver().query(AppProvider.getContentUri(), getMinimalProjection(), null, null, null);
- }
-
- // ========================================================================
- // "Categories"
- // (at this point) not an additional table, but we treat them sort of
- // like they are. That means that if we change the implementation to
- // use a separate table in the future, these should still pass.
- // ========================================================================
-
- public void testCategoriesSingle() {
- insertAppWithCategory("com.dog", "Dog", "Animal");
- insertAppWithCategory("com.rock", "Rock", "Mineral");
- insertAppWithCategory("com.banana", "Banana", "Vegetable");
-
- List categories = AppProvider.Helper.categories(getMockContext());
- String[] expected = new String[] {
- getMockContext().getResources().getString(R.string.category_Whats_New),
- getMockContext().getResources().getString(R.string.category_Recently_Updated),
- getMockContext().getResources().getString(R.string.category_All),
- "Animal",
- "Mineral",
- "Vegetable",
- };
- TestUtils.assertContainsOnly(categories, expected);
- }
-
- public void testCategoriesMultiple() {
- insertAppWithCategory("com.rock.dog", "Rock-Dog", "Mineral,Animal");
- insertAppWithCategory("com.dog.rock.apple", "Dog-Rock-Apple", "Animal,Mineral,Vegetable");
- insertAppWithCategory("com.banana.apple", "Banana", "Vegetable,Vegetable");
-
- List categories = AppProvider.Helper.categories(getMockContext());
- String[] expected = new String[] {
- getMockContext().getResources().getString(R.string.category_Whats_New),
- getMockContext().getResources().getString(R.string.category_Recently_Updated),
- getMockContext().getResources().getString(R.string.category_All),
-
- "Animal",
- "Mineral",
- "Vegetable",
- };
- TestUtils.assertContainsOnly(categories, expected);
-
- insertAppWithCategory("com.example.game", "Game",
- "Running,Shooting,Jumping,Bleh,Sneh,Pleh,Blah,Test category," +
- "The quick brown fox jumps over the lazy dog,With apostrophe's");
-
- List categoriesLonger = AppProvider.Helper.categories(getMockContext());
- String[] expectedLonger = new String[] {
- getMockContext().getResources().getString(R.string.category_Whats_New),
- getMockContext().getResources().getString(R.string.category_Recently_Updated),
- getMockContext().getResources().getString(R.string.category_All),
-
- "Animal",
- "Mineral",
- "Vegetable",
-
- "Running",
- "Shooting",
- "Jumping",
- "Bleh",
- "Sneh",
- "Pleh",
- "Blah",
- "Test category",
- "The quick brown fox jumps over the lazy dog",
- "With apostrophe's",
- };
-
- TestUtils.assertContainsOnly(categoriesLonger, expectedLonger);
- }
-
- // =======================================================================
- // Misc helper functions
- // (to be used by any tests in this suite)
- // =======================================================================
-
- private void insertApp(String id, String name) {
- insertApp(id, name, new ContentValues());
- }
-
- private void insertAppWithCategory(String id, String name, String categories) {
- ContentValues values = new ContentValues(1);
- values.put(AppProvider.DataColumns.CATEGORIES, categories);
- insertApp(id, name, values);
- }
-
- private void insertApp(String id, String name,
- ContentValues additionalValues) {
- TestUtils.insertApp(getMockContentResolver(), id, name, additionalValues);
- }
-
-}
diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/BaseApkProviderTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/BaseApkProviderTest.java
deleted file mode 100644
index f5a73197c..000000000
--- a/app/src/androidTest/java/org/fdroid/fdroid/data/BaseApkProviderTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package org.fdroid.fdroid.data;
-
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-
-import org.fdroid.fdroid.TestUtils;
-
-import java.util.List;
-
-/**
- * Provides helper methods that can be used by both Helper and plain old
- * Provider tests. Allows the test classes to contain only test methods,
- * hopefully making them easier to understand.
- *
- * This should not contain any test methods, or else they get executed
- * once for every concrete subclass.
- */
-abstract class BaseApkProviderTest extends FDroidProviderTest {
-
- BaseApkProviderTest() {
- super(ApkProvider.class, ApkProvider.getAuthority());
- }
-
- @Override
- protected String[] getMinimalProjection() {
- return new String[] {
- ApkProvider.DataColumns.PACKAGE_NAME,
- ApkProvider.DataColumns.VERSION_CODE,
- ApkProvider.DataColumns.NAME,
- ApkProvider.DataColumns.REPO_ID,
- };
- }
-
- protected final Cursor queryAllApks() {
- return getMockContentResolver().query(ApkProvider.getContentUri(), getMinimalProjection(), null, null, null);
- }
-
- protected void assertContains(List apks, Apk apk) {
- boolean found = false;
- for (Apk a : apks) {
- if (a.versionCode == apk.versionCode && a.packageName.equals(apk.packageName)) {
- found = true;
- break;
- }
- }
- if (!found) {
- fail("Apk [" + apk + "] not found in " + TestUtils.listToString(apks));
- }
- }
-
- protected void assertBelongsToApp(Cursor apks, String appId) {
- assertBelongsToApp(ApkProvider.Helper.cursorToList(apks), appId);
- }
-
- protected void assertBelongsToApp(List apks, String appId) {
- for (Apk apk : apks) {
- assertEquals(appId, apk.packageName);
- }
- }
-
- protected void assertTotalApkCount(int expected) {
- assertResultCount(expected, queryAllApks());
- }
-
- protected void assertBelongsToRepo(Cursor apkCursor, long repoId) {
- for (Apk apk : ApkProvider.Helper.cursorToList(apkCursor)) {
- assertEquals(repoId, apk.repo);
- }
- }
-
- protected Apk insertApkForRepo(String id, int versionCode, long repoId) {
- ContentValues additionalValues = new ContentValues();
- additionalValues.put(ApkProvider.DataColumns.REPO_ID, repoId);
- Uri uri = TestUtils.insertApk(this, id, versionCode, additionalValues);
- return ApkProvider.Helper.get(getSwappableContext(), uri);
- }
-}
diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/FDroidProviderTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/FDroidProviderTest.java
deleted file mode 100644
index 40e81fd28..000000000
--- a/app/src/androidTest/java/org/fdroid/fdroid/data/FDroidProviderTest.java
+++ /dev/null
@@ -1,177 +0,0 @@
-package org.fdroid.fdroid.data;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract;
-import android.test.ProviderTestCase2MockContext;
-
-import java.util.List;
-
-import mock.MockContextEmptyComponents;
-import mock.MockContextSwappableComponents;
-import mock.MockFDroidResources;
-
-@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
-public abstract class FDroidProviderTest extends ProviderTestCase2MockContext {
-
- private FDroidProvider[] allProviders = {
- new AppProvider(),
- new RepoProvider(),
- new ApkProvider(),
- new InstalledAppProvider(),
- };
-
- private MockContextSwappableComponents swappableContext;
-
- public FDroidProviderTest(Class providerClass, String providerAuthority) {
- super(providerClass, providerAuthority);
- }
-
- protected Resources getMockResources() {
- return new MockFDroidResources(getContext());
- }
-
- @Override
- 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
- // joins to be tested correctly.
- for (FDroidProvider provider : allProviders) {
- if (!provider.getName().equals(getProvider().getName())) {
- provider.attachInfo(getMockContext(), null);
- getMockContentResolver().addProvider(provider.getName(), provider);
- }
- }
-
- getSwappableContext().setResources(getMockResources());
-
- // The *Provider.Helper.* functions tend to take a Context as their
- // first parameter. This context is used to connect to the relevant
- // content provider. Thus, we need a context that is able to connect
- // to the mock content resolver, in order to reach the provider
- // under test.
- getSwappableContext().setContentResolver(getMockContentResolver());
-
- }
-
- public void testObviouslyInvalidUris() {
- assertInvalidUri("http://www.google.com");
- assertInvalidUri(ContactsContract.AUTHORITY_URI);
- assertInvalidUri("junk");
- }
-
- @Override
- protected Context createMockContext(Context delegate) {
- swappableContext = new MockContextEmptyComponents();
- return swappableContext;
- }
-
- public MockContextSwappableComponents getSwappableContext() {
- return swappableContext;
- }
-
- protected void assertCantDelete(Uri uri) {
- try {
- getMockContentResolver().delete(uri, null, null);
- fail();
- } catch (UnsupportedOperationException e) {
- } catch (Exception e) {
- fail();
- }
- }
-
- protected void assertCantUpdate(Uri uri) {
- try {
- getMockContentResolver().update(uri, new ContentValues(), null, null);
- fail();
- } catch (UnsupportedOperationException e) {
- } catch (Exception e) {
- fail();
- }
- }
-
- protected void assertInvalidUri(String uri) {
- assertInvalidUri(Uri.parse(uri));
- }
-
- protected void assertValidUri(String uri) {
- assertValidUri(Uri.parse(uri));
- }
-
- protected void assertInvalidUri(Uri uri) {
- try {
- // Use getProvdider instead of getContentResolver, because the mock
- // content resolver wont result in the provider we are testing, and
- // hence we don't get to see how our provider responds to invalid
- // uris.
- getProvider().query(uri, getMinimalProjection(), null, null, null);
- fail();
- } catch (UnsupportedOperationException e) { }
- }
-
- protected void assertValidUri(Uri uri) {
- Cursor cursor = getMockContentResolver().query(uri, getMinimalProjection(), null, null, null);
- assertNotNull(cursor);
- cursor.close();
- }
-
- protected void assertValidUri(Uri actualUri, String expectedUri) {
- assertValidUri(actualUri);
- assertEquals(expectedUri, actualUri.toString());
- }
-
- /**
- * Many queries need at least some sort of projection in order to produce
- * valid SQL. As such, we also need to know about that, so we can provide
- * helper functions that revolve around the contnet provider under test.
- */
- protected abstract String[] getMinimalProjection();
-
- protected void assertResultCount(int expectedCount, Uri uri) {
- Cursor cursor = getMockContentResolver().query(uri, getMinimalProjection(), null, null, null);
- assertResultCount(expectedCount, cursor);
- cursor.close();
- }
-
- protected void assertResultCount(int expectedCount, List items) {
- assertNotNull(items);
- assertEquals(expectedCount, items.size());
- }
-
- protected void assertResultCount(int expectedCount, Cursor result) {
- assertNotNull(result);
- assertEquals(expectedCount, result.getCount());
- }
-
- protected void assertIsInstalledVersionInDb(String appId, int versionCode, String versionName) {
- Uri uri = InstalledAppProvider.getAppUri(appId);
-
- String[] projection = {
- InstalledAppProvider.DataColumns.PACKAGE_NAME,
- InstalledAppProvider.DataColumns.VERSION_CODE,
- InstalledAppProvider.DataColumns.VERSION_NAME,
- InstalledAppProvider.DataColumns.APPLICATION_LABEL,
- };
-
- Cursor cursor = getMockContentResolver().query(uri, projection, null, null, null);
-
- assertNotNull(cursor);
- assertEquals("App \"" + appId + "\" not installed", 1, cursor.getCount());
-
- cursor.moveToFirst();
-
- assertEquals(appId, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME)));
- assertEquals(versionCode, cursor.getInt(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_CODE)));
- assertEquals(versionName, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_NAME)));
- cursor.close();
- }
-
-}
diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppCacheTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppCacheTest.java
deleted file mode 100644
index cb63f631a..000000000
--- a/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppCacheTest.java
+++ /dev/null
@@ -1,177 +0,0 @@
-package org.fdroid.fdroid.data;
-
-import mock.MockInstallablePackageManager;
-
-/**
- * Tests the ability of the {@link InstalledAppCacheUpdater} to stay in sync with
- * the {@link android.content.pm.PackageManager}.
- * For practical reasons, it extends FDroidProviderTest, although there is also a
- * separate test for the InstalledAppProvider which tests the CRUD operations in more detail.
- */
-@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
-public class InstalledAppCacheTest extends FDroidProviderTest {
-
- private MockInstallablePackageManager packageManager;
-
- public InstalledAppCacheTest() {
- super(InstalledAppProvider.class, InstalledAppProvider.getAuthority());
- }
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- packageManager = new MockInstallablePackageManager();
- getSwappableContext().setPackageManager(packageManager);
- }
-
- @Override
- protected String[] getMinimalProjection() {
- return new String[] {
- InstalledAppProvider.DataColumns.PACKAGE_NAME,
- };
- }
-
- public void install(String appId, int versionCode, String versionName) {
- packageManager.install(appId, versionCode, versionName);
- }
-
- public void remove(String appId) {
- packageManager.remove(appId);
- }
-
-/* TODO fix me
- public void testFromEmptyCache() {
- assertResultCount(0, InstalledAppProvider.getContentUri());
- for (int i = 1; i <= 15; i ++) {
- install("com.example.app" + i, 200, "2.0");
- }
- InstalledAppCacheUpdater.updateInForeground(getMockContext());
-
- String[] expectedInstalledIds = {
- "com.example.app1",
- "com.example.app2",
- "com.example.app3",
- "com.example.app4",
- "com.example.app5",
- "com.example.app6",
- "com.example.app7",
- "com.example.app8",
- "com.example.app9",
- "com.example.app10",
- "com.example.app11",
- "com.example.app12",
- "com.example.app13",
- "com.example.app14",
- "com.example.app15",
- };
-
- TestUtils.assertContainsOnly(getInstalledAppIdsFromProvider(), expectedInstalledIds);
- }
-
- private String[] getInstalledAppIdsFromProvider() {
- Uri uri = InstalledAppProvider.getContentUri();
- String[] projection = { InstalledAppProvider.DataColumns.PACKAGE_NAME };
- Cursor result = getMockContext().getContentResolver().query(uri, projection, null, null, null);
- if (result == null) {
- return new String[0];
- }
-
- String[] installedAppIds = new String[result.getCount()];
- result.moveToFirst();
- int i = 0;
- while (!result.isAfterLast()) {
- installedAppIds[i] = result.getString(result.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME));
- result.moveToNext();
- i ++;
- }
- result.close();
- return installedAppIds;
- }
-
- public void testAppsAdded() {
- assertResultCount(0, InstalledAppProvider.getContentUri());
-
- install("com.example.app1", 1, "v1");
- install("com.example.app2", 1, "v1");
- install("com.example.app3", 1, "v1");
- InstalledAppCacheUpdater.updateInForeground(getMockContext());
-
- assertResultCount(3, InstalledAppProvider.getContentUri());
- assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
- assertIsInstalledVersionInDb("com.example.app2", 1, "v1");
- assertIsInstalledVersionInDb("com.example.app3", 1, "v1");
-
- install("com.example.app10", 1, "v1");
- install("com.example.app11", 1, "v1");
- install("com.example.app12", 1, "v1");
- InstalledAppCacheUpdater.updateInForeground(getMockContext());
-
- assertResultCount(6, InstalledAppProvider.getContentUri());
- assertIsInstalledVersionInDb("com.example.app10", 1, "v1");
- assertIsInstalledVersionInDb("com.example.app11", 1, "v1");
- assertIsInstalledVersionInDb("com.example.app12", 1, "v1");
- }
-
- public void testAppsRemoved() {
- install("com.example.app1", 1, "v1");
- install("com.example.app2", 1, "v1");
- install("com.example.app3", 1, "v1");
- InstalledAppCacheUpdater.updateInForeground(getMockContext());
-
- assertResultCount(3, InstalledAppProvider.getContentUri());
- assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
- assertIsInstalledVersionInDb("com.example.app2", 1, "v1");
- assertIsInstalledVersionInDb("com.example.app3", 1, "v1");
-
- remove("com.example.app2");
- InstalledAppCacheUpdater.updateInForeground(getMockContext());
-
- assertResultCount(2, InstalledAppProvider.getContentUri());
- assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
- assertIsInstalledVersionInDb("com.example.app3", 1, "v1");
- }
-
- public void testAppsUpdated() {
- install("com.example.app1", 1, "v1");
- install("com.example.app2", 1, "v1");
- InstalledAppCacheUpdater.updateInForeground(getMockContext());
-
- assertResultCount(2, InstalledAppProvider.getContentUri());
- assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
- assertIsInstalledVersionInDb("com.example.app2", 1, "v1");
-
- install("com.example.app2", 20, "v2.0");
- InstalledAppCacheUpdater.updateInForeground(getMockContext());
-
- assertResultCount(2, InstalledAppProvider.getContentUri());
- assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
- assertIsInstalledVersionInDb("com.example.app2", 20, "v2.0");
- }
-
- public void testAppsAddedRemovedAndUpdated() {
- install("com.example.app1", 1, "v1");
- install("com.example.app2", 1, "v1");
- install("com.example.app3", 1, "v1");
- install("com.example.app4", 1, "v1");
- InstalledAppCacheUpdater.updateInForeground(getMockContext());
-
- assertResultCount(4, InstalledAppProvider.getContentUri());
- assertIsInstalledVersionInDb("com.example.app1", 1, "v1");
- assertIsInstalledVersionInDb("com.example.app2", 1, "v1");
- assertIsInstalledVersionInDb("com.example.app3", 1, "v1");
- assertIsInstalledVersionInDb("com.example.app4", 1, "v1");
-
- install("com.example.app1", 13, "v1.3");
- remove("com.example.app2");
- remove("com.example.app3");
- install("com.example.app10", 1, "v1");
- InstalledAppCacheUpdater.updateInForeground(getMockContext());
-
- assertResultCount(3, InstalledAppProvider.getContentUri());
- assertIsInstalledVersionInDb("com.example.app1", 13, "v1.3");
- assertIsInstalledVersionInDb("com.example.app4", 1, "v1");
- assertIsInstalledVersionInDb("com.example.app10", 1, "v1");
-
- }
-*/
-}
diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java
deleted file mode 100644
index 7da03fcd0..000000000
--- a/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java
+++ /dev/null
@@ -1,167 +0,0 @@
-package org.fdroid.fdroid.data;
-
-import android.content.ContentValues;
-import android.content.pm.PackageInfo;
-import android.database.Cursor;
-import android.net.Uri;
-
-import mock.MockContextSwappableComponents;
-import mock.MockInstallablePackageManager;
-
-@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
-public class InstalledAppProviderTest extends FDroidProviderTest {
-
- public InstalledAppProviderTest() {
- super(InstalledAppProvider.class, InstalledAppProvider.getAuthority());
- }
-
- public void testUris() {
- assertInvalidUri(InstalledAppProvider.getAuthority());
- assertInvalidUri(RepoProvider.getContentUri());
- assertInvalidUri(AppProvider.getContentUri());
- assertInvalidUri(ApkProvider.getContentUri());
- assertInvalidUri("blah");
-
- assertValidUri(InstalledAppProvider.getContentUri());
- assertValidUri(InstalledAppProvider.getAppUri("com.example.com"));
- assertValidUri(InstalledAppProvider.getAppUri("blah"));
- }
-
- public void testInsert() {
-
- assertResultCount(0, InstalledAppProvider.getContentUri());
-
- insertInstalledApp("com.example.com1", 1, "v1");
- insertInstalledApp("com.example.com2", 2, "v2");
- insertInstalledApp("com.example.com3", 3, "v3");
-
- assertResultCount(3, InstalledAppProvider.getContentUri());
- assertIsInstalledVersionInDb("com.example.com1", 1, "v1");
- assertIsInstalledVersionInDb("com.example.com2", 2, "v2");
- assertIsInstalledVersionInDb("com.example.com3", 3, "v3");
- }
-
- public void testUpdate() {
-
- insertInstalledApp("com.example.app1", 10, "1.0");
- insertInstalledApp("com.example.app2", 10, "1.0");
-
- assertResultCount(2, InstalledAppProvider.getContentUri());
- assertIsInstalledVersionInDb("com.example.app2", 10, "1.0");
-
- try {
- getMockContentResolver().update(
- InstalledAppProvider.getAppUri("com.example.app2"),
- createContentValues(11, "1.1"),
- null, null
- );
- fail();
- } catch (UnsupportedOperationException e) {
- // We expect this to happen, because we should be using insert() instead.
- }
-
- getMockContentResolver().insert(
- InstalledAppProvider.getContentUri(),
- createContentValues("com.example.app2", 11, "1.1")
- );
-
- assertResultCount(2, InstalledAppProvider.getContentUri());
- assertIsInstalledVersionInDb("com.example.app2", 11, "1.1");
-
- }
-
- public void testLastUpdateTime() {
- String packageName = "com.example.app";
-
- insertInstalledApp(packageName, 10, "1.0");
- assertResultCount(1, InstalledAppProvider.getContentUri());
- assertIsInstalledVersionInDb(packageName, 10, "1.0");
-
- Uri uri = InstalledAppProvider.getAppUri(packageName);
-
- String[] projection = {
- InstalledAppProvider.DataColumns.PACKAGE_NAME,
- InstalledAppProvider.DataColumns.LAST_UPDATE_TIME,
- };
-
- Cursor cursor = getMockContentResolver().query(uri, projection, null, null, null);
- assertNotNull(cursor);
- assertEquals("App \"" + packageName + "\" not installed", 1, cursor.getCount());
- cursor.moveToFirst();
- assertEquals(packageName, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME)));
- long lastUpdateTime = cursor.getLong(cursor.getColumnIndex(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME));
- assertTrue(lastUpdateTime > 0);
- assertTrue(lastUpdateTime < System.currentTimeMillis());
- cursor.close();
-
- insertInstalledApp(packageName, 11, "1.1");
- cursor = getMockContentResolver().query(uri, projection, null, null, null);
- assertNotNull(cursor);
- assertEquals("App \"" + packageName + "\" not installed", 1, cursor.getCount());
- cursor.moveToFirst();
- assertTrue(lastUpdateTime < cursor.getLong(cursor.getColumnIndex(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME)));
- cursor.close();
- }
-
- public void testDelete() {
-
- insertInstalledApp("com.example.app1", 10, "1.0");
- insertInstalledApp("com.example.app2", 10, "1.0");
-
- assertResultCount(2, InstalledAppProvider.getContentUri());
-
- getMockContentResolver().delete(InstalledAppProvider.getAppUri("com.example.app1"), null, null);
-
- assertResultCount(1, InstalledAppProvider.getContentUri());
- assertIsInstalledVersionInDb("com.example.app2", 10, "1.0");
-
- }
-
- @Override
- protected String[] getMinimalProjection() {
- return new String[]{
- InstalledAppProvider.DataColumns.PACKAGE_NAME,
- InstalledAppProvider.DataColumns.VERSION_CODE,
- InstalledAppProvider.DataColumns.VERSION_NAME,
- };
- }
-
- private ContentValues createContentValues(int versionCode, String versionNumber) {
- return createContentValues(null, versionCode, versionNumber);
- }
-
- private ContentValues createContentValues(String appId, int versionCode, String versionNumber) {
- ContentValues values = new ContentValues(3);
- if (appId != null) {
- values.put(InstalledAppProvider.DataColumns.PACKAGE_NAME, appId);
- }
- values.put(InstalledAppProvider.DataColumns.APPLICATION_LABEL, "Mock app: " + appId);
- values.put(InstalledAppProvider.DataColumns.VERSION_CODE, versionCode);
- values.put(InstalledAppProvider.DataColumns.VERSION_NAME, versionNumber);
- values.put(InstalledAppProvider.DataColumns.SIGNATURE, "");
- values.put(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME, System.currentTimeMillis());
- values.put(InstalledAppProvider.DataColumns.HASH_TYPE, "sha256");
- values.put(InstalledAppProvider.DataColumns.HASH, "cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe");
- return values;
- }
-
- private void insertInstalledApp(String appId, int versionCode, String versionNumber) {
- ContentValues values = createContentValues(appId, versionCode, versionNumber);
- getMockContentResolver().insert(InstalledAppProvider.getContentUri(), values);
- }
-
- /**
- * Will tell {@code pm} that we are installing {@code packageName}, and then update the
- * "installed apps" table in the database.
- */
- public static void install(MockContextSwappableComponents context,
- MockInstallablePackageManager pm, String packageName,
- int versionCode, String versionName) {
-
- context.setPackageManager(pm);
- pm.install(packageName, versionCode, versionName);
- PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
- InstalledAppProviderService.insertAppIntoDb(context, packageName, packageInfo);
- }
-
-}
diff --git a/app/src/main/java/org/fdroid/fdroid/Preferences.java b/app/src/main/java/org/fdroid/fdroid/Preferences.java
index 20144be5f..ff521293e 100644
--- a/app/src/main/java/org/fdroid/fdroid/Preferences.java
+++ b/app/src/main/java/org/fdroid/fdroid/Preferences.java
@@ -372,6 +372,17 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
private static Preferences instance;
+ /**
+ * Should only be used for unit testing, whereby separate tests are required to invoke `setup()`.
+ * The reason we don't instead ask for the singleton to be lazily loaded in the {@link Preferences#get()}
+ * method is because that would require each call to that method to require a {@link Context}.
+ * While it is likely that most places asking for preferences have access to a {@link Context},
+ * it is a minor convenience to be able to ask for preferences without.
+ */
+ public static void clearSingletonForTesting() {
+ instance = null;
+ }
+
/**
* Needs to be setup before anything else tries to access it.
*/
diff --git a/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java b/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java
index 683f39235..d72f83210 100644
--- a/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java
+++ b/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java
@@ -190,7 +190,9 @@ public class RepoUpdater {
processXmlProgressListener, new URL(repo.address), (int) indexEntry.getSize());
// Process the index...
- final SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ factory.setNamespaceAware(true);
+ final SAXParser parser = factory.newSAXParser();
final XMLReader reader = parser.getXMLReader();
final RepoXMLHandler repoXMLHandler = new RepoXMLHandler(repo, createIndexReceiver());
reader.setContentHandler(repoXMLHandler);
diff --git a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java
index e1395f92d..70b43b739 100644
--- a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java
+++ b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java
@@ -26,7 +26,7 @@ public class ApkProvider extends FDroidProvider {
* we may want to add additional constraints, so we give our self some
* room by saying only 450 apks can be queried at once.
*/
- protected static final int MAX_APKS_TO_QUERY = 450;
+ static final int MAX_APKS_TO_QUERY = 450;
public static final class Helper {
@@ -319,7 +319,7 @@ public class ApkProvider extends FDroidProvider {
* this directly, think about using
* {@link org.fdroid.fdroid.data.ApkProvider.Helper#knownApks(android.content.Context, java.util.List, String[])}
*/
- protected static Uri getContentUri(List apks) {
+ static Uri getContentUri(List apks) {
return getContentUri().buildUpon()
.appendPath(PATH_APKS)
.appendPath(buildApkString(apks))
diff --git a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java
index f1959596f..3c54f5519 100644
--- a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java
+++ b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java
@@ -156,7 +156,10 @@ public class InstalledAppProviderService extends IntentService {
String packageName = intent.getData().getSchemeSpecificPart();
final String action = intent.getAction();
if (ACTION_INSERT.equals(action)) {
- insertAppIntoDb(this, packageName, (PackageInfo) intent.getParcelableExtra(EXTRA_PACKAGE_INFO));
+ PackageInfo packageInfo = intent.getParcelableExtra(EXTRA_PACKAGE_INFO);
+ String hashType = "sha256";
+ String hash = Utils.getBinaryHash(new File(packageInfo.applicationInfo.publicSourceDir), hashType);
+ insertAppIntoDb(this, packageName, packageInfo, hashType, hash);
} else if (ACTION_DELETE.equals(action)) {
deleteAppFromDb(this, packageName);
}
@@ -164,7 +167,13 @@ public class InstalledAppProviderService extends IntentService {
}
}
- static void insertAppIntoDb(Context context, String packageName, PackageInfo packageInfo) {
+ /**
+ * @param hash Although the has could be calculated within this function, it is helpful to inject
+ * the hash so as to be able to use this method during testing. Otherwise, the
+ * hashing method will try to hash a non-existent .apk file and try to insert NULL
+ * into the database when under test.
+ */
+ static void insertAppIntoDb(Context context, String packageName, PackageInfo packageInfo, String hashType, String hash) {
if (packageInfo == null) {
try {
packageInfo = context.getPackageManager().getPackageInfo(packageName,
@@ -185,8 +194,6 @@ public class InstalledAppProviderService extends IntentService {
contentValues.put(InstalledAppProvider.DataColumns.SIGNATURE, getPackageSig(packageInfo));
contentValues.put(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME, packageInfo.lastUpdateTime);
- String hashType = "sha256";
- String hash = Utils.getBinaryHash(new File(packageInfo.applicationInfo.publicSourceDir), hashType);
contentValues.put(InstalledAppProvider.DataColumns.HASH_TYPE, hashType);
contentValues.put(InstalledAppProvider.DataColumns.HASH, hash);
diff --git a/app/src/test/java/org/fdroid/fdroid/AcceptableMultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/AcceptableMultiRepoUpdaterTest.java
new file mode 100644
index 000000000..d159c6018
--- /dev/null
+++ b/app/src/test/java/org/fdroid/fdroid/AcceptableMultiRepoUpdaterTest.java
@@ -0,0 +1,82 @@
+
+package org.fdroid.fdroid;
+
+import android.util.Log;
+
+import org.fdroid.fdroid.RepoUpdater.UpdateException;
+import org.fdroid.fdroid.data.Repo;
+import org.fdroid.fdroid.data.RepoProvider;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+@Config(constants = BuildConfig.class)
+@RunWith(RobolectricGradleTestRunner.class)
+public class AcceptableMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
+ private static final String TAG = "AcceptableMultiRepoTest";
+
+ private void assertSomewhatAcceptable() {
+ Log.i(TAG, "Asserting at least one versions of each .apk is in index.");
+ List repos = RepoProvider.Helper.all(context);
+ assertEquals("Repos", 3, repos.size());
+
+ assertApp2048();
+ assertAppAdaway();
+ assertAppAdbWireless();
+ assertAppIcsImport();
+ }
+
+ @Test
+ public void testAcceptableConflictingThenMainThenArchive() throws UpdateException {
+ assertEmpty();
+ if (updateConflicting() && updateMain() && updateArchive()) {
+ assertSomewhatAcceptable();
+ }
+ }
+
+ @Test
+ public void testAcceptableConflictingThenArchiveThenMain() throws UpdateException {
+ assertEmpty();
+ if (updateConflicting() && updateArchive() && updateMain()) {
+ assertSomewhatAcceptable();
+ }
+ }
+
+ @Test
+ public void testAcceptableArchiveThenMainThenConflicting() throws UpdateException {
+ assertEmpty();
+ if (updateArchive() && updateMain() && updateConflicting()) {
+ assertSomewhatAcceptable();
+ }
+ }
+
+ @Test
+ public void testAcceptableArchiveThenConflictingThenMain() throws UpdateException {
+ assertEmpty();
+ if (updateArchive() && updateConflicting() && updateMain()) {
+ assertSomewhatAcceptable();
+ }
+ }
+
+ @Test
+ public void testAcceptableMainThenArchiveThenConflicting() throws UpdateException {
+ assertEmpty();
+ if (updateMain() && updateArchive() && updateConflicting()) {
+ assertSomewhatAcceptable();
+ }
+ }
+
+ @Test
+ public void testAcceptableMainThenConflictingThenArchive() throws UpdateException {
+ assertEmpty();
+ if (updateMain() && updateConflicting() && updateArchive()) {
+ assertSomewhatAcceptable();
+ }
+ }
+
+}
diff --git a/app/src/test/java/org/fdroid/fdroid/Assert.java b/app/src/test/java/org/fdroid/fdroid/Assert.java
new file mode 100644
index 000000000..1296e0f9c
--- /dev/null
+++ b/app/src/test/java/org/fdroid/fdroid/Assert.java
@@ -0,0 +1,225 @@
+package org.fdroid.fdroid;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+import junit.framework.AssertionFailedError;
+
+import org.fdroid.fdroid.data.ApkProvider;
+import org.fdroid.fdroid.data.AppProvider;
+import org.fdroid.fdroid.data.InstalledAppProvider;
+import org.robolectric.shadows.ShadowContentResolver;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+public class Assert {
+
+ public static void assertContainsOnly(List actualList, T[] expectedArray) {
+ List expectedList = new ArrayList<>(expectedArray.length);
+ Collections.addAll(expectedList, expectedArray);
+ assertContainsOnly(actualList, expectedList);
+ }
+
+ public static void assertContainsOnly(T[] actualArray, List expectedList) {
+ List actualList = new ArrayList<>(actualArray.length);
+ Collections.addAll(actualList, actualArray);
+ assertContainsOnly(actualList, expectedList);
+ }
+
+ public static void assertContainsOnly(T[] actualArray, T[] expectedArray) {
+ List expectedList = new ArrayList<>(expectedArray.length);
+ Collections.addAll(expectedList, expectedArray);
+ assertContainsOnly(actualArray, expectedList);
+ }
+
+ public static String listToString(List list) {
+ String string = "[";
+ for (int i = 0; i < list.size(); i++) {
+ if (i > 0) {
+ string += ", ";
+ }
+ string += "'" + list.get(i) + "'";
+ }
+ string += "]";
+ return string;
+ }
+
+ public static void assertContainsOnly(List actualList, List expectedContains) {
+ if (actualList.size() != expectedContains.size()) {
+ String message =
+ "List sizes don't match.\n" +
+ "Expected: " +
+ listToString(expectedContains) + "\n" +
+ "Actual: " +
+ listToString(actualList);
+ throw new AssertionFailedError(message);
+ }
+ for (T required : expectedContains) {
+ boolean containsRequired = false;
+ for (T itemInList : actualList) {
+ if (required.equals(itemInList)) {
+ containsRequired = true;
+ break;
+ }
+ }
+ if (!containsRequired) {
+ String message =
+ "List doesn't contain \"" + required + "\".\n" +
+ "Expected: " +
+ listToString(expectedContains) + "\n" +
+ "Actual: " +
+ listToString(actualList);
+ throw new AssertionFailedError(message);
+ }
+ }
+ }
+
+ public static void assertCantDelete(ShadowContentResolver resolver, Uri uri) {
+ try {
+ resolver.delete(uri, null, null);
+ fail();
+ } catch (UnsupportedOperationException e) {
+ // Successful condition
+ } catch (Exception e) {
+ fail();
+ }
+ }
+
+ public static void assertCantUpdate(ShadowContentResolver resolver, Uri uri) {
+ try {
+ resolver.update(uri, new ContentValues(), null, null);
+ fail();
+ } catch (UnsupportedOperationException e) {
+ // Successful condition
+ } catch (Exception e) {
+ fail();
+ }
+ }
+
+ public static void assertInvalidUri(ShadowContentResolver resolver, String uri) {
+ assertInvalidUri(resolver, Uri.parse(uri));
+ }
+
+ public static void assertValidUri(ShadowContentResolver resolver, String uri, String[] projection) {
+ assertValidUri(resolver, Uri.parse(uri), projection);
+ }
+
+ public static void assertInvalidUri(ShadowContentResolver resolver, Uri uri) {
+ Cursor cursor = resolver.query(uri, new String[] {}, null, null, null);
+ assertNull(cursor);
+ }
+
+ public static void assertValidUri(ShadowContentResolver resolver, Uri uri, String[] projection) {
+ Cursor cursor = resolver.query(uri, projection, null, null, null);
+ assertNotNull(cursor);
+ cursor.close();
+ }
+
+ public static void assertValidUri(ShadowContentResolver resolver, Uri actualUri, String expectedUri, String[] projection) {
+ assertValidUri(resolver, actualUri, projection);
+ assertEquals(expectedUri, actualUri.toString());
+ }
+
+ public static void assertResultCount(ShadowContentResolver resolver, int expectedCount, Uri uri) {
+ assertResultCount(resolver, expectedCount, uri, new String[] {});
+ }
+
+ public static void assertResultCount(ShadowContentResolver resolver, int expectedCount, Uri uri, String[] projection) {
+ Cursor cursor = resolver.query(uri, projection, null, null, null);
+ assertResultCount(expectedCount, cursor);
+ cursor.close();
+ }
+
+ public static void assertResultCount(int expectedCount, List items) {
+ assertNotNull(items);
+ assertEquals(expectedCount, items.size());
+ }
+
+ public static void assertResultCount(int expectedCount, Cursor result) {
+ assertNotNull(result);
+ assertEquals(expectedCount, result.getCount());
+ }
+
+ public static void assertIsInstalledVersionInDb(ShadowContentResolver resolver, String appId, int versionCode, String versionName) {
+ Uri uri = InstalledAppProvider.getAppUri(appId);
+
+ String[] projection = {
+ InstalledAppProvider.DataColumns.PACKAGE_NAME,
+ InstalledAppProvider.DataColumns.VERSION_CODE,
+ InstalledAppProvider.DataColumns.VERSION_NAME,
+ InstalledAppProvider.DataColumns.APPLICATION_LABEL,
+ };
+
+ Cursor cursor = resolver.query(uri, projection, null, null, null);
+
+ assertNotNull(cursor);
+ assertEquals("App \"" + appId + "\" not installed", 1, cursor.getCount());
+
+ cursor.moveToFirst();
+
+ assertEquals(appId, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME)));
+ assertEquals(versionCode, cursor.getInt(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_CODE)));
+ assertEquals(versionName, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_NAME)));
+ cursor.close();
+ }
+
+ public static void insertApp(ShadowContentResolver resolver, String appId, String name) {
+ insertApp(resolver, appId, name, new ContentValues());
+ }
+
+ public static void insertApp(ShadowContentResolver resolver, String id, String name, ContentValues additionalValues) {
+
+ ContentValues values = new ContentValues();
+ values.put(AppProvider.DataColumns.PACKAGE_NAME, id);
+ values.put(AppProvider.DataColumns.NAME, name);
+
+ // Required fields (NOT NULL in the database).
+ values.put(AppProvider.DataColumns.SUMMARY, "test summary");
+ values.put(AppProvider.DataColumns.DESCRIPTION, "test description");
+ values.put(AppProvider.DataColumns.LICENSE, "GPL?");
+ values.put(AppProvider.DataColumns.IS_COMPATIBLE, 1);
+ values.put(AppProvider.DataColumns.IGNORE_ALLUPDATES, 0);
+ values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, 0);
+
+ values.putAll(additionalValues);
+
+ Uri uri = AppProvider.getContentUri();
+
+ resolver.insert(uri, values);
+ }
+
+ public static Uri insertApk(ShadowContentResolver resolver, String id, int versionCode) {
+ return insertApk(resolver, id, versionCode, new ContentValues());
+ }
+
+ public static Uri insertApk(ShadowContentResolver resolver, String id, int versionCode, ContentValues additionalValues) {
+
+ ContentValues values = new ContentValues();
+
+ values.put(ApkProvider.DataColumns.PACKAGE_NAME, id);
+ values.put(ApkProvider.DataColumns.VERSION_CODE, versionCode);
+
+ // Required fields (NOT NULL in the database).
+ values.put(ApkProvider.DataColumns.REPO_ID, 1);
+ values.put(ApkProvider.DataColumns.VERSION_NAME, "The good one");
+ values.put(ApkProvider.DataColumns.HASH, "11111111aaaaaaaa");
+ values.put(ApkProvider.DataColumns.NAME, "Test Apk");
+ values.put(ApkProvider.DataColumns.SIZE, 10000);
+ values.put(ApkProvider.DataColumns.IS_COMPATIBLE, 1);
+
+ values.putAll(additionalValues);
+
+ Uri uri = ApkProvider.getContentUri();
+
+ return resolver.insert(uri, values);
+ }
+
+}
diff --git a/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java
new file mode 100644
index 000000000..2da14d8eb
--- /dev/null
+++ b/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java
@@ -0,0 +1,201 @@
+
+package org.fdroid.fdroid;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+
+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.FDroidProviderTest;
+import org.fdroid.fdroid.data.Repo;
+import org.fdroid.fdroid.data.RepoProvider;
+import org.junit.After;
+import org.junit.Before;
+
+import java.io.File;
+import java.util.List;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public abstract class MultiRepoUpdaterTest extends FDroidProviderTest {
+ @SuppressWarnings("unused")
+ private static final String TAG = "AcceptableMultiRepoUpdaterTest"; // NOPMD
+
+ protected static final String REPO_MAIN = "Test F-Droid repo";
+ protected static final String REPO_ARCHIVE = "Test F-Droid repo (Archive)";
+ protected static final String REPO_CONFLICTING = "Test F-Droid repo with different apps";
+
+ protected RepoUpdater conflictingRepoUpdater;
+ protected RepoUpdater mainRepoUpdater;
+ protected RepoUpdater archiveRepoUpdater;
+
+ private static final String PUB_KEY =
+ "3082050b308202f3a003020102020420d8f212300d06092a864886f70d01010b050030363110300e0603" +
+ "55040b1307462d44726f69643122302006035504031319657073696c6f6e2e70657465722e7365727779" +
+ "6c6f2e636f6d301e170d3135303931323233313632315a170d3433303132383233313632315a30363110" +
+ "300e060355040b1307462d44726f69643122302006035504031319657073696c6f6e2e70657465722e73" +
+ "657277796c6f2e636f6d30820222300d06092a864886f70d01010105000382020f003082020a02820201" +
+ "00b21fe72b84ce721967851364bd20511088d117bc3034e4bb4d3c1a06af2a308fdffdaf63b12e0926b9" +
+ "0545134b9ff570646cbcad89d9e86dcc8eb9977dd394240c75bccf5e8ddc3c5ef91b4f16eca5f36c36f1" +
+ "92463ff2c9257d3053b7c9ecdd1661bd01ec3fe70ee34a7e6b92ddba04f258a32d0cfb1b0ce85d047180" +
+ "97fc4bdfb54541b430dfcfc1c84458f9eb5627e0ec5341d561c3f15f228379a1282d241329198f31a7ac" +
+ "cd51ab2bbb881a1da55001123483512f77275f8990c872601198065b4e0137ddd1482e4fdefc73b857d4" +
+ "be324ca96c268ceb725398f8cc38a0dc6aa2c277f8686724e8c7ff3f320a05791fccacc6caa956cf23a9" +
+ "de2dc7070b262c0e35d90d17e90773bb11e875e79a8dfd958e359d5d5ad903a7cbc2955102502bd0134c" +
+ "a1ff7a0bbbbb57302e4a251e40724dcaa8ad024f4b3a71b8fceaac664c0dcc1995a1c4cf42676edad8bc" +
+ "b03ba255ab796677f18fff2298e1aaa5b134254b44d08a4d934c9859af7bbaf078c37b7f628db0e2cffb" +
+ "0493a669d5f4770d35d71284550ce06d6f6811cd2a31585085716257a4ba08ad968b0a2bf88f34ca2f2c" +
+ "73af1c042ab147597faccfb6516ef4468cfa0c5ab3c8120eaa7bac1080e4d2310f717db20815d0e1ee26" +
+ "bd4e47eed8d790892017ae9595365992efa1b7fd1bc1963f018264b2b3749b8f7b1907bb0843f1e7fc2d" +
+ "3f3b02284cd4bae0ab0203010001a321301f301d0603551d0e0416041456110e4fed863ab1df9448bfd9" +
+ "e10a8bc32ffe08300d06092a864886f70d01010b050003820201008082572ae930ebc55ecf1110f4bb72" +
+ "ad2a952c8ac6e65bd933706beb4a310e23deabb8ef6a7e93eea8217ab1f3f57b1f477f95f1d62eccb563" +
+ "67a4d70dfa6fcd2aace2bb00b90af39412a9441a9fae2396ff8b93de1df3d9837c599b1f80b7d75285cb" +
+ "df4539d7dd9612f54b45ca59bc3041c9b92fac12753fac154d12f31df360079ab69a2d20db9f6a7277a8" +
+ "259035e93de95e8cbc80351bc83dd24256183ea5e3e1db2a51ea314cdbc120c064b77e2eb3a731530511" +
+ "1e1dabed6996eb339b7cb948d05c1a84d63094b4a4c6d11389b2a7b5f2d7ecc9a149dda6c33705ef2249" +
+ "58afdfa1d98cf646dcf8857cd8342b1e07d62cb4313f35ad209046a4a42ff73f38cc740b1e695eeda49d" +
+ "5ea0384ad32f9e3ae54f6a48a558dbc7cccabd4e2b2286dc9c804c840bd02b9937841a0e48db00be9e3c" +
+ "d7120cf0f8648ce4ed63923f0352a2a7b3b97fc55ba67a7a218b8c0b3cda4a45861280a622e0a59cc9fb" +
+ "ca1117568126c581afa4408b0f5c50293c212c406b8ab8f50aad5ed0f038cfca580ef3aba7df25464d9e" +
+ "495ffb629922cfb511d45e6294c045041132452f1ed0f20ac3ab4792f610de1734e4c8b71d743c4b0101" +
+ "98f848e0dbfce5a0f2da0198c47e6935a47fda12c518ef45adfb66ddf5aebaab13948a66c004b8592d22" +
+ "e8af60597c4ae2977977cf61dc715a572e241ae717cafdb4f71781943945ac52e0f50b";
+
+ @Before
+ public final void setupMultiRepo() throws Exception {
+ // On a fresh database install, there will be F-Droid + GP repos, including their Archive
+ // repos that we are not interested in.
+ RepoProvider.Helper.remove(context, 1);
+ RepoProvider.Helper.remove(context, 2);
+ RepoProvider.Helper.remove(context, 3);
+ RepoProvider.Helper.remove(context, 4);
+
+ conflictingRepoUpdater = createUpdater(REPO_CONFLICTING, context);
+ mainRepoUpdater = createUpdater(REPO_MAIN, context);
+ archiveRepoUpdater = createUpdater(REPO_ARCHIVE, context);
+
+ Preferences.setup(context);
+ }
+
+ @After
+ public final void tearDownMultiRepo() {
+ Preferences.clearSingletonForTesting();
+ }
+
+ protected void assertApp(String packageName, int[] versionCodes) {
+ List apks = ApkProvider.Helper.findByPackageName(context, packageName, ApkProvider.DataColumns.ALL);
+ assertApksExist(apks, packageName, versionCodes);
+ }
+
+ protected void assertApp2048() {
+ assertApp("com.uberspot.a2048", new int[]{19, 18});
+ }
+
+ protected void assertAppAdaway() {
+ assertApp("org.adaway", new int[]{54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 42, 40, 38, 37, 36, 35});
+ }
+
+ protected void assertAppAdbWireless() {
+ assertApp("siir.es.adbWireless", new int[]{12});
+ }
+
+ protected void assertAppIcsImport() {
+ assertApp("org.dgtale.icsimport", new int[]{3, 2});
+ }
+
+ @NonNull
+ protected Repo findRepo(@NonNull String name, List allRepos) {
+ Repo repo = null;
+ for (Repo r : allRepos) {
+ if (TextUtils.equals(name, r.getName())) {
+ repo = r;
+ break;
+ }
+ }
+
+ assertNotNull("Repo " + allRepos, repo);
+ return repo;
+ }
+
+ /**
+ * Checks that each version of appId as specified in versionCodes is present in apksToCheck.
+ */
+ protected void assertApksExist(List apksToCheck, String appId, int[] versionCodes) {
+ for (int versionCode : versionCodes) {
+ boolean found = false;
+ for (Apk apk : apksToCheck) {
+ if (apk.versionCode == versionCode && apk.packageName.equals(appId)) {
+ found = true;
+ break;
+ }
+ }
+
+ assertTrue("Couldn't find app " + appId + ", v" + versionCode, found);
+ }
+ }
+
+ protected void assertEmpty() {
+ assertEquals("No apps present", 0, AppProvider.Helper.all(context.getContentResolver()).size());
+
+ String[] packages = {
+ "com.uberspot.a2048",
+ "org.adaway",
+ "siir.es.adbWireless",
+ };
+
+ for (String id : packages) {
+ assertEquals("No apks for " + id, 0, ApkProvider.Helper.findByPackageName(context, id).size());
+ }
+ }
+
+ private RepoUpdater createUpdater(String name, Context context) {
+ Repo repo = new Repo();
+ repo.signingCertificate = PUB_KEY;
+ repo.address = "https://fake.url/" + UUID.randomUUID().toString() + "/fdroid/repo";
+ repo.name = name;
+
+ ContentValues values = new ContentValues(2);
+ values.put(RepoProvider.DataColumns.SIGNING_CERT, repo.signingCertificate);
+ values.put(RepoProvider.DataColumns.ADDRESS, repo.address);
+ values.put(RepoProvider.DataColumns.NAME, repo.name);
+
+ RepoProvider.Helper.insert(context, values);
+
+ // Need to reload the repo based on address so that it includes the primary key from
+ // the database.
+ return new RepoUpdater(context, RepoProvider.Helper.findByAddress(context, repo.address));
+ }
+
+ protected boolean updateConflicting() throws UpdateException {
+ return updateRepo(conflictingRepoUpdater, "multiRepo.conflicting.jar");
+ }
+
+ protected boolean updateMain() throws UpdateException {
+ return updateRepo(mainRepoUpdater, "multiRepo.normal.jar");
+ }
+
+ protected boolean updateArchive() throws UpdateException {
+ return updateRepo(archiveRepoUpdater, "multiRepo.archive.jar");
+ }
+
+ private boolean updateRepo(RepoUpdater updater, String indexJarPath) throws UpdateException {
+ File indexJar = TestUtils.copyResourceToTempFile(indexJarPath);
+ try {
+ updater.processDownloadedFile(indexJar);
+ } finally {
+ if (indexJar != null && indexJar.exists()) {
+ assertTrue(indexJar.delete());
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/app/src/test/java/org/fdroid/fdroid/ProperMultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/ProperMultiRepoUpdaterTest.java
new file mode 100644
index 000000000..b0e992854
--- /dev/null
+++ b/app/src/test/java/org/fdroid/fdroid/ProperMultiRepoUpdaterTest.java
@@ -0,0 +1,154 @@
+
+package org.fdroid.fdroid;
+
+import android.util.Log;
+
+import org.fdroid.fdroid.data.Apk;
+import org.fdroid.fdroid.data.ApkProvider;
+import org.fdroid.fdroid.data.Repo;
+import org.fdroid.fdroid.data.RepoProvider;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+/*
+At time fo writing, the following tests did not pass. This is because the multi-repo support
+in F-Droid was not sufficient. When working on proper multi repo support than this should be
+uncommented and all these tests will be required to pass:
+
+@Config(constants = BuildConfig.class)
+@RunWith(RobolectricGradleTestRunner.class)
+*/
+@SuppressWarnings("unused")
+public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest {
+ private static final String TAG = "ProperMultiRepoSupport";
+
+ /*@Test
+ public void testCorrectConflictingThenMainThenArchive() throws UpdateException {
+ assertEmpty();
+ if (updateConflicting() && updateMain() && updateArchive()) {
+ assertExpected();
+ }
+ }
+
+ @Test
+ public void testCorrectConflictingThenArchiveThenMain() throws UpdateException {
+ assertEmpty();
+ if (updateConflicting() && updateArchive() && updateMain()) {
+ assertExpected();
+ }
+ }
+
+ @Test
+ public void testCorrectArchiveThenMainThenConflicting() throws UpdateException {
+ assertEmpty();
+ if (updateArchive() && updateMain() && updateConflicting()) {
+ assertExpected();
+ }
+ }
+
+ @Test
+ public void testCorrectArchiveThenConflictingThenMain() throws UpdateException {
+ assertEmpty();
+ if (updateArchive() && updateConflicting() && updateMain()) {
+ assertExpected();
+ }
+ }
+
+ @Test
+ public void testCorrectMainThenArchiveThenConflicting() throws UpdateException {
+ assertEmpty();
+ if (updateMain() && updateArchive() && updateConflicting()) {
+ assertExpected();
+ }
+ }
+
+ @Test
+ public void testCorrectMainThenConflictingThenArchive() throws UpdateException {
+ assertEmpty();
+ if (updateMain() && updateConflicting() && updateArchive()) {
+ assertExpected();
+ }
+ }*/
+
+ /**
+ * Check that all of the expected apps and apk versions are available in the database. This
+ * check will take into account the repository the apks came from, to ensure that each
+ * repository indeed contains the apks that it said it would provide.
+ */
+ private void assertExpected() {
+ Log.i(TAG, "Asserting all versions of each .apk are in index.");
+ List repos = RepoProvider.Helper.all(context);
+ assertEquals("Repos", 3, repos.size());
+
+ assertMainRepo(repos);
+ assertMainArchiveRepo(repos);
+ assertConflictingRepo(repos);
+ }
+
+ /**
+ * + 2048 (com.uberspot.a2048)
+ * - Version 1.96 (19)
+ * - Version 1.95 (18)
+ * + AdAway (org.adaway)
+ * - Version 3.0.2 (54)
+ * - Version 3.0.1 (53)
+ * - Version 3.0 (52)
+ * + adbWireless (siir.es.adbWireless)
+ * - Version 1.5.4 (12)
+ */
+ private void assertMainRepo(List allRepos) {
+ Repo repo = findRepo(REPO_MAIN, allRepos);
+
+ List apks = ApkProvider.Helper.findByRepo(context, repo, ApkProvider.DataColumns.ALL);
+ assertEquals("Apks for main repo", apks.size(), 6);
+ assertApksExist(apks, "com.uberspot.a2048", new int[]{18, 19});
+ assertApksExist(apks, "org.adaway", new int[]{52, 53, 54});
+ assertApksExist(apks, "siir.es.adbWireless", new int[]{12});
+ }
+
+ /**
+ * + AdAway (org.adaway)
+ * - Version 2.9.2 (51)
+ * - Version 2.9.1 (50)
+ * - Version 2.9 (49)
+ * - Version 2.8.1 (48)
+ * - Version 2.8 (47)
+ * - Version 2.7 (46)
+ * - Version 2.6 (45)
+ * - Version 2.3 (42)
+ * - Version 2.1 (40)
+ * - Version 1.37 (38)
+ * - Version 1.36 (37)
+ * - Version 1.35 (36)
+ * - Version 1.34 (35)
+ */
+ private void assertMainArchiveRepo(List allRepos) {
+ Repo repo = findRepo(REPO_ARCHIVE, allRepos);
+
+ List apks = ApkProvider.Helper.findByRepo(context, repo, ApkProvider.DataColumns.ALL);
+ assertEquals("Apks for main archive repo", 13, apks.size());
+ assertApksExist(apks, "org.adaway", new int[]{35, 36, 37, 38, 40, 42, 45, 46, 47, 48, 49, 50, 51});
+ }
+
+ /**
+ * + AdAway (org.adaway)
+ * - Version 3.0.1 (53) *
+ * - Version 3.0 (52) *
+ * - Version 2.9.2 (51) *
+ * - Version 2.2.1 (50) *
+ * + Add to calendar (org.dgtale.icsimport)
+ * - Version 1.2 (3)
+ * - Version 1.1 (2)
+ */
+ private void assertConflictingRepo(List allRepos) {
+ Repo repo = findRepo(REPO_CONFLICTING, allRepos);
+
+ List apks = ApkProvider.Helper.findByRepo(context, repo, ApkProvider.DataColumns.ALL);
+ assertEquals("Apks for main repo", 6, apks.size());
+ assertApksExist(apks, "org.adaway", new int[]{50, 51, 52, 53});
+ assertApksExist(apks, "org.dgtale.icsimport", new int[]{2, 3});
+ }
+
+}
diff --git a/app/src/androidTest/java/org/fdroid/fdroid/RepoXMLHandlerTest.java b/app/src/test/java/org/fdroid/fdroid/RepoXMLHandlerTest.java
similarity index 99%
rename from app/src/androidTest/java/org/fdroid/fdroid/RepoXMLHandlerTest.java
rename to app/src/test/java/org/fdroid/fdroid/RepoXMLHandlerTest.java
index 8b2b7f6f1..e354320e8 100644
--- a/app/src/androidTest/java/org/fdroid/fdroid/RepoXMLHandlerTest.java
+++ b/app/src/test/java/org/fdroid/fdroid/RepoXMLHandlerTest.java
@@ -2,7 +2,6 @@
package org.fdroid.fdroid;
import android.support.annotation.NonNull;
-import android.support.test.runner.AndroidJUnit4;
import android.text.TextUtils;
import android.util.Log;
@@ -12,6 +11,8 @@ import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.mock.MockRepo;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.annotation.Config;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
@@ -31,7 +32,8 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
-@RunWith(AndroidJUnit4.class)
+@Config(constants = BuildConfig.class)
+@RunWith(RobolectricGradleTestRunner.class)
public class RepoXMLHandlerTest {
private static final String TAG = "RepoXMLHandlerTest";
@@ -673,16 +675,16 @@ public class RepoXMLHandlerTest {
@NonNull
private RepoDetails getFromFile(String indexFilename) {
- SAXParser parser;
try {
- parser = SAXParserFactory.newInstance().newSAXParser();
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ factory.setNamespaceAware(true);
+ SAXParser parser = factory.newSAXParser();
XMLReader reader = parser.getXMLReader();
RepoDetails repoDetails = new RepoDetails();
RepoXMLHandler handler = new RepoXMLHandler(new MockRepo(100), repoDetails);
reader.setContentHandler(handler);
- String resName = "assets/" + indexFilename;
- Log.i(TAG, "test file: " + getClass().getClassLoader().getResource(resName));
- InputStream input = getClass().getClassLoader().getResourceAsStream(resName);
+ Log.i(TAG, "test file: " + getClass().getClassLoader().getResource(indexFilename));
+ InputStream input = getClass().getClassLoader().getResourceAsStream(indexFilename);
InputSource is = new InputSource(new BufferedInputStream(input));
reader.parse(is);
return repoDetails;
diff --git a/app/src/test/java/org/fdroid/fdroid/TestFDroidApp.java b/app/src/test/java/org/fdroid/fdroid/TestFDroidApp.java
new file mode 100644
index 000000000..15bef648f
--- /dev/null
+++ b/app/src/test/java/org/fdroid/fdroid/TestFDroidApp.java
@@ -0,0 +1,13 @@
+package org.fdroid.fdroid;
+
+import android.app.Application;
+
+/**
+ * Due to there being so much static initialization in the main FDroidApp, it becomes hard to reset
+ * that state between Robolectric test runs. Therefore, robolectric tests will default to this
+ * {@link Application} instead of {@link FDroidApp}. It intentionally doesn't extends {@link FDroidApp}
+ * so that the static initialization in {@link FDroidApp#onCreate()} is not executed.
+ */
+public class TestFDroidApp extends Application {
+
+}
diff --git a/app/src/test/java/org/fdroid/fdroid/TestUtils.java b/app/src/test/java/org/fdroid/fdroid/TestUtils.java
new file mode 100644
index 000000000..2ced7a025
--- /dev/null
+++ b/app/src/test/java/org/fdroid/fdroid/TestUtils.java
@@ -0,0 +1,40 @@
+package org.fdroid.fdroid;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class TestUtils {
+
+ @SuppressWarnings("unused")
+ private static final String TAG = "TestUtils"; // NOPMD
+
+ public static File copyResourceToTempFile(String resourceName) {
+ File tempFile = null;
+ InputStream input = null;
+ OutputStream output = null;
+ try {
+ tempFile = File.createTempFile(resourceName + "-", ".testasset");
+ input = TestUtils.class.getClassLoader().getResourceAsStream(resourceName);
+ output = new FileOutputStream(tempFile);
+ Utils.copy(input, output);
+ } catch (IOException e) {
+ e.printStackTrace();
+ if (tempFile != null && tempFile.exists()) {
+ assertTrue(tempFile.delete());
+ }
+ fail();
+ return null;
+ } finally {
+ Utils.closeQuietly(output);
+ Utils.closeQuietly(input);
+ }
+ return tempFile;
+ }
+
+}
diff --git a/app/src/androidTest/java/org/fdroid/fdroid/UtilsTest.java b/app/src/test/java/org/fdroid/fdroid/UtilsTest.java
similarity index 97%
rename from app/src/androidTest/java/org/fdroid/fdroid/UtilsTest.java
rename to app/src/test/java/org/fdroid/fdroid/UtilsTest.java
index 96f701328..7ec649ca3 100644
--- a/app/src/androidTest/java/org/fdroid/fdroid/UtilsTest.java
+++ b/app/src/test/java/org/fdroid/fdroid/UtilsTest.java
@@ -1,14 +1,14 @@
package org.fdroid.fdroid;
-import android.app.Instrumentation;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
import org.apache.commons.io.FileUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
import java.io.File;
import java.io.IOException;
@@ -17,7 +17,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-@RunWith(AndroidJUnit4.class)
+@Config(constants = BuildConfig.class)
+@RunWith(RobolectricGradleTestRunner.class)
public class UtilsTest {
String fdroidFingerprint = "43238D512C1E5EB2D6569F4A3AFBF5523418B82E0A3ED1552770ABB9A9C9CCAB";
@@ -50,7 +51,7 @@ public class UtilsTest {
@Test
public void testFormatFingerprint() {
- Context context = InstrumentationRegistry.getTargetContext();
+ Context context = RuntimeEnvironment.application;
String badResult = Utils.formatFingerprint(context, "");
// real fingerprints
String formatted;
@@ -145,22 +146,29 @@ public class UtilsTest {
@Test
public void testClearOldFiles() throws IOException, InterruptedException {
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- File dir = new File(TestUtils.getWriteableDir(instrumentation), "clearOldFiles");
+ File tempDir = new File(System.getProperty("java.io.tmpdir"));
+ assertTrue(tempDir.isDirectory());
+ assertTrue(tempDir.canWrite());
+
+ File dir = new File(tempDir, "F-Droid-test.clearOldFiles");
FileUtils.deleteQuietly(dir);
- dir.mkdirs();
+ assertTrue(dir.mkdirs());
assertTrue(dir.isDirectory());
File first = new File(dir, "first");
+ first.deleteOnExit();
+
File second = new File(dir, "second");
+ second.deleteOnExit();
+
assertFalse(first.exists());
assertFalse(second.exists());
- first.createNewFile();
+ assertTrue(first.createNewFile());
assertTrue(first.exists());
Thread.sleep(7000);
- second.createNewFile();
+ assertTrue(second.createNewFile());
assertTrue(second.exists());
Utils.clearOldFiles(dir, 3);
diff --git a/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java
new file mode 100644
index 000000000..03e7ebfa0
--- /dev/null
+++ b/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java
@@ -0,0 +1,541 @@
+package org.fdroid.fdroid.data;
+
+import android.app.Application;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+import org.fdroid.fdroid.Assert;
+import org.fdroid.fdroid.BuildConfig;
+import org.fdroid.fdroid.Utils;
+import org.fdroid.fdroid.mock.MockApk;
+import org.fdroid.fdroid.mock.MockApp;
+import org.fdroid.fdroid.mock.MockRepo;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import static org.fdroid.fdroid.Assert.assertCantDelete;
+import static org.fdroid.fdroid.Assert.assertContainsOnly;
+import static org.fdroid.fdroid.Assert.assertResultCount;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+@Config(constants = BuildConfig.class, application = Application.class)
+@RunWith(RobolectricGradleTestRunner.class)
+public class ApkProviderTest extends FDroidProviderTest {
+
+ private static final String[] PROJ = ApkProvider.DataColumns.ALL;
+
+ @Test
+ public void testAppApks() {
+ for (int i = 1; i <= 10; i++) {
+ Assert.insertApk(contentResolver, "org.fdroid.fdroid", i);
+ Assert.insertApk(contentResolver, "com.example", i);
+ }
+
+ assertTotalApkCount(20);
+
+ Cursor fdroidApks = contentResolver.query(
+ ApkProvider.getAppUri("org.fdroid.fdroid"),
+ PROJ,
+ null, null, null);
+ assertResultCount(10, fdroidApks);
+ assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
+ fdroidApks.close();
+
+ Cursor exampleApks = contentResolver.query(
+ ApkProvider.getAppUri("com.example"),
+ PROJ,
+ null, null, null);
+ assertResultCount(10, exampleApks);
+ assertBelongsToApp(exampleApks, "com.example");
+ exampleApks.close();
+
+ ApkProvider.Helper.deleteApksByApp(context, new MockApp("com.example"));
+
+ Cursor all = queryAllApks();
+ assertResultCount(10, all);
+ assertBelongsToApp(all, "org.fdroid.fdroid");
+ all.close();
+ }
+
+ @Test
+ public void testDeleteArbitraryApks() {
+ Apk one = insertApkForRepo("com.example.one", 1, 10);
+ Apk two = insertApkForRepo("com.example.two", 1, 10);
+ Apk three = insertApkForRepo("com.example.three", 1, 10);
+ Apk four = insertApkForRepo("com.example.four", 1, 10);
+ Apk five = insertApkForRepo("com.example.five", 1, 10);
+
+ assertTotalApkCount(5);
+
+ assertEquals("com.example.one", one.packageName);
+ assertEquals("com.example.two", two.packageName);
+ assertEquals("com.example.five", five.packageName);
+
+ String[] expectedIds = {
+ "com.example.one",
+ "com.example.two",
+ "com.example.three",
+ "com.example.four",
+ "com.example.five",
+ };
+
+ List all = ApkProvider.Helper.findByRepo(context, new MockRepo(10), ApkProvider.DataColumns.ALL);
+ List actualIds = new ArrayList<>();
+ for (Apk apk : all) {
+ actualIds.add(apk.packageName);
+ }
+
+ assertContainsOnly(actualIds, expectedIds);
+
+ List toDelete = new ArrayList<>(3);
+ toDelete.add(two);
+ toDelete.add(three);
+ toDelete.add(four);
+ ApkProvider.Helper.deleteApks(context, toDelete);
+
+ assertTotalApkCount(2);
+
+ List allRemaining = ApkProvider.Helper.findByRepo(context, new MockRepo(10), ApkProvider.DataColumns.ALL);
+ List actualRemainingIds = new ArrayList<>();
+ for (Apk apk : allRemaining) {
+ actualRemainingIds.add(apk.packageName);
+ }
+
+ String[] expectedRemainingIds = {
+ "com.example.one",
+ "com.example.five",
+ };
+
+ assertContainsOnly(actualRemainingIds, expectedRemainingIds);
+ }
+
+ @Test
+ public void testInvalidDeleteUris() {
+ Apk apk = new MockApk("org.fdroid.fdroid", 10);
+
+ assertCantDelete(contentResolver, ApkProvider.getContentUri());
+ assertCantDelete(contentResolver, ApkProvider.getContentUri("org.fdroid.fdroid", 10));
+ assertCantDelete(contentResolver, ApkProvider.getContentUri(apk));
+ assertCantDelete(contentResolver, Uri.withAppendedPath(ApkProvider.getContentUri(), "some-random-path"));
+ }
+
+ private static final long REPO_KEEP = 1;
+ private static final long REPO_DELETE = 2;
+
+ @Test
+ public void testRepoApks() {
+
+ // Insert apks into two repos, one of which we will later purge the
+ // the apks from.
+ for (int i = 1; i <= 5; i++) {
+ insertApkForRepo("org.fdroid.fdroid", i, REPO_KEEP);
+ insertApkForRepo("com.example." + i, 1, REPO_DELETE);
+ }
+ for (int i = 6; i <= 10; i++) {
+ insertApkForRepo("org.fdroid.fdroid", i, REPO_DELETE);
+ insertApkForRepo("com.example." + i, 1, REPO_KEEP);
+ }
+
+ assertTotalApkCount(20);
+
+ Cursor cursor = contentResolver.query(
+ ApkProvider.getRepoUri(REPO_DELETE), PROJ, null, null, null);
+ assertResultCount(10, cursor);
+ assertBelongsToRepo(cursor, REPO_DELETE);
+ cursor.close();
+
+ int count = ApkProvider.Helper.deleteApksByRepo(context, new MockRepo(REPO_DELETE));
+ assertEquals(10, count);
+
+ assertTotalApkCount(10);
+ cursor = contentResolver.query(
+ ApkProvider.getRepoUri(REPO_DELETE), PROJ, null, null, null);
+ assertResultCount(0, cursor);
+ cursor.close();
+
+ // The only remaining apks should be those from REPO_KEEP.
+ assertBelongsToRepo(queryAllApks(), REPO_KEEP);
+ }
+
+ @Test
+ public void testQuery() {
+ Cursor cursor = queryAllApks();
+ assertNotNull(cursor);
+ cursor.close();
+ }
+
+ @Test
+ public void testInsert() {
+
+ // Start with an empty database...
+ Cursor cursor = queryAllApks();
+ assertNotNull(cursor);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+
+ Apk apk = new MockApk("org.fdroid.fdroid", 13);
+
+ // Insert a new record...
+ Uri newUri = Assert.insertApk(contentResolver, apk.packageName, apk.versionCode);
+ assertEquals(ApkProvider.getContentUri(apk).toString(), newUri.toString());
+ cursor = queryAllApks();
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+
+ // And now we should be able to recover these values from the apk
+ // value object (because the queryAllApks() helper asks for VERSION_CODE and
+ // PACKAGE_NAME.
+ cursor.moveToFirst();
+ Apk toCheck = new Apk(cursor);
+ cursor.close();
+ assertEquals("org.fdroid.fdroid", toCheck.packageName);
+ assertEquals(13, toCheck.versionCode);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testCursorMustMoveToFirst() {
+ Assert.insertApk(contentResolver, "org.example.test", 12);
+ Cursor cursor = queryAllApks();
+ new Apk(cursor);
+ }
+
+ @Test
+ public void testCount() {
+ String[] projectionCount = new String[] {ApkProvider.DataColumns._COUNT};
+
+ for (int i = 0; i < 13; i++) {
+ Assert.insertApk(contentResolver, "com.example", i);
+ }
+
+ Uri all = ApkProvider.getContentUri();
+ Cursor allWithFields = contentResolver.query(all, PROJ, null, null, null);
+ Cursor allWithCount = contentResolver.query(all, projectionCount, null, null, null);
+
+ assertResultCount(13, allWithFields);
+ allWithFields.close();
+ assertResultCount(1, allWithCount);
+
+ allWithCount.moveToFirst();
+ int countColumn = allWithCount.getColumnIndex(ApkProvider.DataColumns._COUNT);
+ assertEquals(13, allWithCount.getInt(countColumn));
+ allWithCount.close();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInsertWithInvalidExtraFieldDescription() {
+ assertInvalidExtraField(RepoProvider.DataColumns.DESCRIPTION);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInsertWithInvalidExtraFieldAddress() {
+ assertInvalidExtraField(RepoProvider.DataColumns.ADDRESS);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInsertWithInvalidExtraFieldFingerprint() {
+ assertInvalidExtraField(RepoProvider.DataColumns.FINGERPRINT);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInsertWithInvalidExtraFieldName() {
+ assertInvalidExtraField(RepoProvider.DataColumns.NAME);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInsertWithInvalidExtraFieldSigningCert() {
+ assertInvalidExtraField(RepoProvider.DataColumns.SIGNING_CERT);
+ }
+
+ public void assertInvalidExtraField(String field) {
+ ContentValues invalidRepo = new ContentValues();
+ invalidRepo.put(field, "Test data");
+ Assert.insertApk(contentResolver, "org.fdroid.fdroid", 10, invalidRepo);
+ }
+
+ @Test
+ public void testInsertWithValidExtraFields() {
+
+ assertResultCount(0, queryAllApks());
+
+ ContentValues values = new ContentValues();
+ values.put(ApkProvider.DataColumns.REPO_ID, 10);
+ values.put(ApkProvider.DataColumns.REPO_ADDRESS, "http://example.com");
+ values.put(ApkProvider.DataColumns.REPO_VERSION, 3);
+ values.put(ApkProvider.DataColumns.FEATURES, "Some features");
+ Uri uri = Assert.insertApk(contentResolver, "com.example.com", 1, values);
+
+ assertResultCount(1, queryAllApks());
+
+ String[] projections = ApkProvider.DataColumns.ALL;
+ Cursor cursor = contentResolver.query(uri, projections, null, null, null);
+ cursor.moveToFirst();
+ Apk apk = new Apk(cursor);
+ cursor.close();
+
+ // These should have quietly been dropped when we tried to save them,
+ // because the provider only knows how to query them (not update them).
+ assertEquals(null, apk.repoAddress);
+ assertEquals(0, apk.repoVersion);
+
+ // But this should have saved correctly...
+ assertEquals("Some features", apk.features.toString());
+ assertEquals("com.example.com", apk.packageName);
+ assertEquals(1, apk.versionCode);
+ assertEquals(10, apk.repo);
+ }
+
+ @Test
+ public void testKnownApks() {
+
+ for (int i = 0; i < 7; i++) {
+ Assert.insertApk(contentResolver, "org.fdroid.fdroid", i);
+ }
+
+ for (int i = 0; i < 9; i++) {
+ Assert.insertApk(contentResolver, "org.example", i);
+ }
+
+ for (int i = 0; i < 3; i++) {
+ Assert.insertApk(contentResolver, "com.example", i);
+ }
+
+ Assert.insertApk(contentResolver, "com.apk.thingo", 1);
+
+ Apk[] known = {
+ new MockApk("org.fdroid.fdroid", 1),
+ new MockApk("org.fdroid.fdroid", 3),
+ new MockApk("org.fdroid.fdroid", 5),
+
+ new MockApk("com.example", 1),
+ new MockApk("com.example", 2),
+ };
+
+ Apk[] unknown = {
+ new MockApk("org.fdroid.fdroid", 7),
+ new MockApk("org.fdroid.fdroid", 9),
+ new MockApk("org.fdroid.fdroid", 11),
+ new MockApk("org.fdroid.fdroid", 13),
+
+ new MockApk("com.example", 3),
+ new MockApk("com.example", 4),
+ new MockApk("com.example", 5),
+
+ new MockApk("info.example", 1),
+ new MockApk("info.example", 2),
+ };
+
+ List apksToCheck = new ArrayList<>(known.length + unknown.length);
+ Collections.addAll(apksToCheck, known);
+ Collections.addAll(apksToCheck, unknown);
+
+ String[] projection = {
+ ApkProvider.DataColumns.PACKAGE_NAME,
+ ApkProvider.DataColumns.VERSION_CODE,
+ };
+
+ List knownApks = ApkProvider.Helper.knownApks(context, apksToCheck, projection);
+
+ assertResultCount(known.length, knownApks);
+
+ for (Apk knownApk : knownApks) {
+ assertContains(knownApks, knownApk);
+ }
+ }
+
+ @Test
+ public void testFindByApp() {
+
+ for (int i = 0; i < 7; i++) {
+ Assert.insertApk(contentResolver, "org.fdroid.fdroid", i);
+ }
+
+ for (int i = 0; i < 9; i++) {
+ Assert.insertApk(contentResolver, "org.example", i);
+ }
+
+ for (int i = 0; i < 3; i++) {
+ Assert.insertApk(contentResolver, "com.example", i);
+ }
+
+ Assert.insertApk(contentResolver, "com.apk.thingo", 1);
+
+ assertTotalApkCount(7 + 9 + 3 + 1);
+
+ List fdroidApks = ApkProvider.Helper.findByPackageName(context, "org.fdroid.fdroid");
+ assertResultCount(7, fdroidApks);
+ assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
+
+ List exampleApks = ApkProvider.Helper.findByPackageName(context, "org.example");
+ assertResultCount(9, exampleApks);
+ assertBelongsToApp(exampleApks, "org.example");
+
+ List exampleApks2 = ApkProvider.Helper.findByPackageName(context, "com.example");
+ assertResultCount(3, exampleApks2);
+ assertBelongsToApp(exampleApks2, "com.example");
+
+ List thingoApks = ApkProvider.Helper.findByPackageName(context, "com.apk.thingo");
+ assertResultCount(1, thingoApks);
+ assertBelongsToApp(thingoApks, "com.apk.thingo");
+ }
+
+ @Test
+ public void testUpdate() {
+
+ Uri apkUri = Assert.insertApk(contentResolver, "com.example", 10);
+
+ String[] allFields = ApkProvider.DataColumns.ALL;
+ Cursor cursor = contentResolver.query(apkUri, allFields, null, null, null);
+ assertResultCount(1, cursor);
+
+ cursor.moveToFirst();
+ Apk apk = new Apk(cursor);
+ cursor.close();
+
+ assertEquals("com.example", apk.packageName);
+ assertEquals(10, apk.versionCode);
+
+ assertNull(apk.features);
+ assertNull(apk.added);
+ assertNull(apk.hashType);
+
+ apk.features = Utils.CommaSeparatedList.make("one,two,three");
+ long dateTimestamp = System.currentTimeMillis();
+ apk.added = new Date(dateTimestamp);
+ apk.hashType = "i'm a hash type";
+
+ ApkProvider.Helper.update(context, apk);
+
+ // Should not have inserted anything else, just updated the already existing apk.
+ Cursor allCursor = contentResolver.query(ApkProvider.getContentUri(), allFields, null, null, null);
+ assertResultCount(1, allCursor);
+ allCursor.close();
+
+ Cursor updatedCursor = contentResolver.query(apkUri, allFields, null, null, null);
+ assertResultCount(1, updatedCursor);
+
+ updatedCursor.moveToFirst();
+ Apk updatedApk = new Apk(updatedCursor);
+ updatedCursor.close();
+
+ assertEquals("com.example", updatedApk.packageName);
+ assertEquals(10, updatedApk.versionCode);
+
+ assertNotNull(updatedApk.features);
+ assertNotNull(updatedApk.added);
+ assertNotNull(updatedApk.hashType);
+
+ assertEquals("one,two,three", updatedApk.features.toString());
+ assertEquals(new Date(dateTimestamp).getYear(), updatedApk.added.getYear());
+ assertEquals(new Date(dateTimestamp).getMonth(), updatedApk.added.getMonth());
+ assertEquals(new Date(dateTimestamp).getDay(), updatedApk.added.getDay());
+ assertEquals("i'm a hash type", updatedApk.hashType);
+ }
+
+ @Test
+ public void testFind() {
+ // Insert some random apks either side of the "com.example", so that
+ // the Helper.find() method doesn't stumble upon the app we are interested
+ // in by shear dumb luck...
+ for (int i = 0; i < 10; i++) {
+ Assert.insertApk(contentResolver, "org.fdroid.apk." + i, i);
+ }
+
+ ContentValues values = new ContentValues();
+ values.put(ApkProvider.DataColumns.VERSION_NAME, "v1.1");
+ values.put(ApkProvider.DataColumns.HASH, "xxxxyyyy");
+ values.put(ApkProvider.DataColumns.HASH_TYPE, "a hash type");
+ Assert.insertApk(contentResolver, "com.example", 11, values);
+
+ // ...and a few more for good measure...
+ for (int i = 15; i < 20; i++) {
+ Assert.insertApk(contentResolver, "com.other.thing." + i, i);
+ }
+
+ Apk apk = ApkProvider.Helper.find(context, "com.example", 11);
+
+ assertNotNull(apk);
+
+ // The find() method populates ALL fields if you don't specify any,
+ // so we expect to find each of the ones we inserted above...
+ assertEquals("com.example", apk.packageName);
+ assertEquals(11, apk.versionCode);
+ assertEquals("v1.1", apk.versionName);
+ assertEquals("xxxxyyyy", apk.hash);
+ assertEquals("a hash type", apk.hashType);
+
+ String[] projection = {
+ ApkProvider.DataColumns.PACKAGE_NAME,
+ ApkProvider.DataColumns.HASH,
+ };
+
+ Apk apkLessFields = ApkProvider.Helper.find(context, "com.example", 11, projection);
+
+ assertNotNull(apkLessFields);
+
+ assertEquals("com.example", apkLessFields.packageName);
+ assertEquals("xxxxyyyy", apkLessFields.hash);
+
+ // Didn't ask for these fields, so should be their default values...
+ assertNull(apkLessFields.hashType);
+ assertNull(apkLessFields.versionName);
+ assertEquals(0, apkLessFields.versionCode);
+
+ Apk notFound = ApkProvider.Helper.find(context, "com.doesnt.exist", 1000);
+ assertNull(notFound);
+ }
+
+ protected final Cursor queryAllApks() {
+ return contentResolver.query(ApkProvider.getContentUri(), PROJ, null, null, null);
+ }
+
+ protected void assertContains(List apks, Apk apk) {
+ boolean found = false;
+ for (Apk a : apks) {
+ if (a.versionCode == apk.versionCode && a.packageName.equals(apk.packageName)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ fail("Apk [" + apk + "] not found in " + Assert.listToString(apks));
+ }
+ }
+
+ protected void assertBelongsToApp(Cursor apks, String appId) {
+ assertBelongsToApp(ApkProvider.Helper.cursorToList(apks), appId);
+ }
+
+ protected void assertBelongsToApp(List apks, String appId) {
+ for (Apk apk : apks) {
+ assertEquals(appId, apk.packageName);
+ }
+ }
+
+ protected void assertTotalApkCount(int expected) {
+ assertResultCount(expected, queryAllApks());
+ }
+
+ protected void assertBelongsToRepo(Cursor apkCursor, long repoId) {
+ for (Apk apk : ApkProvider.Helper.cursorToList(apkCursor)) {
+ assertEquals(repoId, apk.repo);
+ }
+ }
+
+ protected Apk insertApkForRepo(String id, int versionCode, long repoId) {
+ ContentValues additionalValues = new ContentValues();
+ additionalValues.put(ApkProvider.DataColumns.REPO_ID, repoId);
+ Uri uri = Assert.insertApk(contentResolver, id, versionCode, additionalValues);
+ return ApkProvider.Helper.get(context, uri);
+ }
+}
diff --git a/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java
new file mode 100644
index 000000000..9178eb2c3
--- /dev/null
+++ b/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java
@@ -0,0 +1,369 @@
+package org.fdroid.fdroid.data;
+
+import android.app.Application;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+import org.fdroid.fdroid.BuildConfig;
+import org.fdroid.fdroid.R;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowContentResolver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.fdroid.fdroid.Assert.assertContainsOnly;
+import static org.fdroid.fdroid.Assert.assertResultCount;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+@Config(constants = BuildConfig.class, application = Application.class)
+@RunWith(RobolectricGradleTestRunner.class)
+public class AppProviderTest extends FDroidProviderTest {
+
+ private static final String[] PROJ = AppProvider.DataColumns.ALL;
+
+ @Before
+ public void setup() {
+ ShadowContentResolver.registerProvider(AppProvider.getAuthority(), new AppProvider());
+ }
+
+ /**
+ * Although this doesn't directly relate to the {@link AppProvider}, it is here because
+ * the {@link AppProvider} used to stumble across this bug when asking for installed apps,
+ * and the device had over 1000 apps installed.
+ */
+ @Test
+ public void testMaxSqliteParams() {
+ insertApp("com.example.app1", "App 1");
+ insertApp("com.example.app100", "App 100");
+ insertApp("com.example.app1000", "App 1000");
+
+ for (int i = 0; i < 50; i++) {
+ InstalledAppTestUtils.install(context, "com.example.app" + i, 1, "v1");
+ }
+ assertResultCount(contentResolver, 1, AppProvider.getInstalledUri(), PROJ);
+
+ for (int i = 50; i < 500; i++) {
+ InstalledAppTestUtils.install(context, "com.example.app" + i, 1, "v1");
+ }
+ assertResultCount(contentResolver, 2, AppProvider.getInstalledUri(), PROJ);
+
+ for (int i = 500; i < 1100; i++) {
+ InstalledAppTestUtils.install(context, "com.example.app" + i, 1, "v1");
+ }
+ assertResultCount(contentResolver, 3, AppProvider.getInstalledUri(), PROJ);
+ }
+
+ @Test
+ public void testCantFindApp() {
+ assertNull(AppProvider.Helper.findByPackageName(context.getContentResolver(), "com.example.doesnt-exist"));
+ }
+
+ @Test
+ public void testQuery() {
+ Cursor cursor = queryAllApps();
+ assertNotNull(cursor);
+ cursor.close();
+ }
+
+ private void insertApps(int count) {
+ for (int i = 0; i < count; i++) {
+ insertApp("com.example.test." + i, "Test app " + i);
+ }
+ }
+
+ private void insertAndInstallApp(
+ String packageName, int installedVercode, int suggestedVercode,
+ boolean ignoreAll, int ignoreVercode) {
+ ContentValues values = new ContentValues(3);
+ values.put(AppProvider.DataColumns.SUGGESTED_VERSION_CODE, suggestedVercode);
+ values.put(AppProvider.DataColumns.IGNORE_ALLUPDATES, ignoreAll);
+ values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, ignoreVercode);
+ insertApp(packageName, "App: " + packageName, values);
+
+ InstalledAppTestUtils.install(context, packageName, installedVercode, "v" + installedVercode);
+ }
+
+ @Test
+ public void testCanUpdate() {
+ insertApp("not installed", "not installed");
+ insertAndInstallApp("installed, only one version available", 1, 1, false, 0);
+ insertAndInstallApp("installed, already latest, no ignore", 10, 10, false, 0);
+ insertAndInstallApp("installed, already latest, ignore all", 10, 10, true, 0);
+ insertAndInstallApp("installed, already latest, ignore latest", 10, 10, false, 10);
+ insertAndInstallApp("installed, already latest, ignore old", 10, 10, false, 5);
+ insertAndInstallApp("installed, old version, no ignore", 5, 10, false, 0);
+ insertAndInstallApp("installed, old version, ignore all", 5, 10, true, 0);
+ insertAndInstallApp("installed, old version, ignore latest", 5, 10, false, 10);
+ insertAndInstallApp("installed, old version, ignore newer, but not latest", 5, 10, false, 8);
+
+ ContentResolver r = context.getContentResolver();
+
+ // Can't "update", although can "install"...
+ App notInstalled = AppProvider.Helper.findByPackageName(r, "not installed");
+ assertFalse(notInstalled.canAndWantToUpdate());
+
+ App installedOnlyOneVersionAvailable = AppProvider.Helper.findByPackageName(r, "installed, only one version available");
+ App installedAlreadyLatestNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, already latest, no ignore");
+ App installedAlreadyLatestIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore all");
+ App installedAlreadyLatestIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore latest");
+ App installedAlreadyLatestIgnoreOld = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore old");
+
+ assertFalse(installedOnlyOneVersionAvailable.canAndWantToUpdate());
+ assertFalse(installedAlreadyLatestNoIgnore.canAndWantToUpdate());
+ assertFalse(installedAlreadyLatestIgnoreAll.canAndWantToUpdate());
+ assertFalse(installedAlreadyLatestIgnoreLatest.canAndWantToUpdate());
+ assertFalse(installedAlreadyLatestIgnoreOld.canAndWantToUpdate());
+
+ App installedOldNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, old version, no ignore");
+ App installedOldIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore all");
+ App installedOldIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore latest");
+ App installedOldIgnoreNewerNotLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore newer, but not latest");
+
+ assertTrue(installedOldNoIgnore.canAndWantToUpdate());
+ assertFalse(installedOldIgnoreAll.canAndWantToUpdate());
+ assertFalse(installedOldIgnoreLatest.canAndWantToUpdate());
+ assertTrue(installedOldIgnoreNewerNotLatest.canAndWantToUpdate());
+
+ Cursor canUpdateCursor = r.query(AppProvider.getCanUpdateUri(), AppProvider.DataColumns.ALL, null, null, null);
+ assertNotNull(canUpdateCursor);
+ canUpdateCursor.moveToFirst();
+ List canUpdateIds = new ArrayList<>(canUpdateCursor.getCount());
+ while (!canUpdateCursor.isAfterLast()) {
+ canUpdateIds.add(new App(canUpdateCursor).packageName);
+ canUpdateCursor.moveToNext();
+ }
+ canUpdateCursor.close();
+
+ String[] expectedUpdateableIds = {
+ "installed, old version, no ignore",
+ "installed, old version, ignore newer, but not latest",
+ };
+
+ assertContainsOnly(expectedUpdateableIds, canUpdateIds);
+ }
+
+ @Test
+ public void testIgnored() {
+ insertApp("not installed", "not installed");
+ insertAndInstallApp("installed, only one version available", 1, 1, false, 0);
+ insertAndInstallApp("installed, already latest, no ignore", 10, 10, false, 0);
+ insertAndInstallApp("installed, already latest, ignore all", 10, 10, true, 0);
+ insertAndInstallApp("installed, already latest, ignore latest", 10, 10, false, 10);
+ insertAndInstallApp("installed, already latest, ignore old", 10, 10, false, 5);
+ insertAndInstallApp("installed, old version, no ignore", 5, 10, false, 0);
+ insertAndInstallApp("installed, old version, ignore all", 5, 10, true, 0);
+ insertAndInstallApp("installed, old version, ignore latest", 5, 10, false, 10);
+ insertAndInstallApp("installed, old version, ignore newer, but not latest", 5, 10, false, 8);
+
+ assertResultCount(contentResolver, 10, AppProvider.getContentUri(), PROJ);
+
+ String[] projection = {AppProvider.DataColumns.PACKAGE_NAME};
+ List ignoredApps = AppProvider.Helper.findIgnored(context, projection);
+
+ String[] expectedIgnored = {
+ "installed, already latest, ignore all",
+ "installed, already latest, ignore latest",
+ // NOT "installed, already latest, ignore old" - because it
+ // is should only ignore if "ignored version" is >= suggested
+
+ "installed, old version, ignore all",
+ "installed, old version, ignore latest",
+ // NOT "installed, old version, ignore newer, but not latest"
+ // for the same reason as above.
+ };
+
+ assertContainsOnlyIds(ignoredApps, expectedIgnored);
+ }
+
+ private void assertContainsOnlyIds(List actualApps, String[] expectedIds) {
+ List actualIds = new ArrayList<>(actualApps.size());
+ for (App app : actualApps) {
+ actualIds.add(app.packageName);
+ }
+ assertContainsOnly(actualIds, expectedIds);
+ }
+
+ @Test
+ public void testInstalled() {
+ insertApps(100);
+
+ assertResultCount(contentResolver, 100, AppProvider.getContentUri(), PROJ);
+ assertResultCount(contentResolver, 0, AppProvider.getInstalledUri(), PROJ);
+
+ for (int i = 10; i < 20; i++) {
+ InstalledAppTestUtils.install(context, "com.example.test." + i, i, "v1");
+ }
+
+ assertResultCount(contentResolver, 10, AppProvider.getInstalledUri(), PROJ);
+ }
+
+ @Test
+ public void testInsert() {
+
+ // Start with an empty database...
+ Cursor cursor = queryAllApps();
+ assertNotNull(cursor);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+
+ // Insert a new record...
+ insertApp("org.fdroid.fdroid", "F-Droid");
+ cursor = queryAllApps();
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+
+ // And now we should be able to recover these values from the app
+ // value object (because the queryAllApps() helper asks for NAME and
+ // PACKAGE_NAME.
+ cursor.moveToFirst();
+ App app = new App(cursor);
+ cursor.close();
+ assertEquals("org.fdroid.fdroid", app.packageName);
+ assertEquals("F-Droid", app.name);
+
+ App otherApp = AppProvider.Helper.findByPackageName(context.getContentResolver(), "org.fdroid.fdroid");
+ assertNotNull(otherApp);
+ assertEquals("org.fdroid.fdroid", otherApp.packageName);
+ assertEquals("F-Droid", otherApp.name);
+ }
+
+ /**
+ * We intentionally throw an IllegalArgumentException if you haven't
+ * yet called cursor.move*().
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCursorMustMoveToFirst() {
+ insertApp("org.fdroid.fdroid", "F-Droid");
+ Cursor cursor = queryAllApps();
+ new App(cursor);
+ }
+
+ private Cursor queryAllApps() {
+ String[] projection = new String[] {
+ AppProvider.DataColumns._ID,
+ AppProvider.DataColumns.NAME,
+ AppProvider.DataColumns.PACKAGE_NAME,
+ };
+ return contentResolver.query(AppProvider.getContentUri(), projection, null, null, null);
+ }
+
+
+ // ========================================================================
+ // "Categories"
+ // (at this point) not an additional table, but we treat them sort of
+ // like they are. That means that if we change the implementation to
+ // use a separate table in the future, these should still pass.
+ // ========================================================================
+
+ @Test
+ public void testCategoriesSingle() {
+ insertAppWithCategory("com.dog", "Dog", "Animal");
+ insertAppWithCategory("com.rock", "Rock", "Mineral");
+ insertAppWithCategory("com.banana", "Banana", "Vegetable");
+
+ List categories = AppProvider.Helper.categories(context);
+ String[] expected = new String[] {
+ context.getResources().getString(R.string.category_Whats_New),
+ context.getResources().getString(R.string.category_Recently_Updated),
+ context.getResources().getString(R.string.category_All),
+ "Animal",
+ "Mineral",
+ "Vegetable",
+ };
+ assertContainsOnly(categories, expected);
+ }
+
+ @Test
+ public void testCategoriesMultiple() {
+ insertAppWithCategory("com.rock.dog", "Rock-Dog", "Mineral,Animal");
+ insertAppWithCategory("com.dog.rock.apple", "Dog-Rock-Apple", "Animal,Mineral,Vegetable");
+ insertAppWithCategory("com.banana.apple", "Banana", "Vegetable,Vegetable");
+
+ List categories = AppProvider.Helper.categories(context);
+ String[] expected = new String[] {
+ context.getResources().getString(R.string.category_Whats_New),
+ context.getResources().getString(R.string.category_Recently_Updated),
+ context.getResources().getString(R.string.category_All),
+
+ "Animal",
+ "Mineral",
+ "Vegetable",
+ };
+ assertContainsOnly(categories, expected);
+
+ insertAppWithCategory("com.example.game", "Game",
+ "Running,Shooting,Jumping,Bleh,Sneh,Pleh,Blah,Test category," +
+ "The quick brown fox jumps over the lazy dog,With apostrophe's");
+
+ List categoriesLonger = AppProvider.Helper.categories(context);
+ String[] expectedLonger = new String[] {
+ context.getResources().getString(R.string.category_Whats_New),
+ context.getResources().getString(R.string.category_Recently_Updated),
+ context.getResources().getString(R.string.category_All),
+
+ "Animal",
+ "Mineral",
+ "Vegetable",
+
+ "Running",
+ "Shooting",
+ "Jumping",
+ "Bleh",
+ "Sneh",
+ "Pleh",
+ "Blah",
+ "Test category",
+ "The quick brown fox jumps over the lazy dog",
+ "With apostrophe's",
+ };
+
+ assertContainsOnly(categoriesLonger, expectedLonger);
+ }
+
+ // =======================================================================
+ // Misc helper functions
+ // (to be used by any tests in this suite)
+ // =======================================================================
+
+ private void insertApp(String id, String name) {
+ insertApp(id, name, new ContentValues());
+ }
+
+ private void insertAppWithCategory(String id, String name, String categories) {
+ ContentValues values = new ContentValues(1);
+ values.put(AppProvider.DataColumns.CATEGORIES, categories);
+ insertApp(id, name, values);
+ }
+
+ public void insertApp(String id, String name, ContentValues additionalValues) {
+
+ ContentValues values = new ContentValues();
+ values.put(AppProvider.DataColumns.PACKAGE_NAME, id);
+ values.put(AppProvider.DataColumns.NAME, name);
+
+ // Required fields (NOT NULL in the database).
+ values.put(AppProvider.DataColumns.SUMMARY, "test summary");
+ values.put(AppProvider.DataColumns.DESCRIPTION, "test description");
+ values.put(AppProvider.DataColumns.LICENSE, "GPL?");
+ values.put(AppProvider.DataColumns.IS_COMPATIBLE, 1);
+ values.put(AppProvider.DataColumns.IGNORE_ALLUPDATES, 0);
+ values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, 0);
+
+ values.putAll(additionalValues);
+
+ Uri uri = AppProvider.getContentUri();
+
+ contentResolver.insert(uri, values);
+ }
+}
diff --git a/app/src/test/java/org/fdroid/fdroid/data/FDroidProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/FDroidProviderTest.java
new file mode 100644
index 000000000..adddf695c
--- /dev/null
+++ b/app/src/test/java/org/fdroid/fdroid/data/FDroidProviderTest.java
@@ -0,0 +1,38 @@
+package org.fdroid.fdroid.data;
+
+import android.content.ContentResolver;
+import android.content.ContextWrapper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.mockito.AdditionalAnswers;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.shadows.ShadowContentResolver;
+
+import static org.mockito.Mockito.mock;
+
+public abstract class FDroidProviderTest {
+
+ protected ShadowContentResolver contentResolver;
+ protected ContextWrapper context;
+
+ @Before
+ public final void setupBase() {
+ contentResolver = Shadows.shadowOf(RuntimeEnvironment.application.getContentResolver());
+ final ContentResolver resolver = mock(ContentResolver.class, AdditionalAnswers.delegatesTo(contentResolver));
+ context = new ContextWrapper(RuntimeEnvironment.application.getApplicationContext()) {
+ @Override
+ public ContentResolver getContentResolver() {
+ return resolver;
+ }
+ };
+ ShadowContentResolver.registerProvider(AppProvider.getAuthority(), new AppProvider());
+ }
+
+ @After
+ public final void tearDownBase() {
+ FDroidProvider.clearDbHelperSingleton();
+ }
+
+}
diff --git a/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java
new file mode 100644
index 000000000..e207f1a16
--- /dev/null
+++ b/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java
@@ -0,0 +1,189 @@
+package org.fdroid.fdroid.data;
+
+import android.app.Application;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+import org.fdroid.fdroid.BuildConfig;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowContentResolver;
+
+import static org.fdroid.fdroid.Assert.assertIsInstalledVersionInDb;
+import static org.fdroid.fdroid.Assert.assertResultCount;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Map;
+
+@Config(constants = BuildConfig.class, application = Application.class)
+@RunWith(RobolectricGradleTestRunner.class)
+public class InstalledAppProviderTest extends FDroidProviderTest {
+
+ @Before
+ public void setup() {
+ ShadowContentResolver.registerProvider(InstalledAppProvider.getAuthority(), new InstalledAppProvider());
+ }
+
+ @Test
+ public void insertSingleApp() {
+ Map foundBefore = InstalledAppProvider.Helper.all(RuntimeEnvironment.application);
+ assertEquals(foundBefore.size(), 0);
+
+ ContentValues values = new ContentValues();
+ values.put(InstalledAppProvider.DataColumns.PACKAGE_NAME, "org.example.test-app");
+ values.put(InstalledAppProvider.DataColumns.APPLICATION_LABEL, "Test App");
+ values.put(InstalledAppProvider.DataColumns.VERSION_CODE, 1021);
+ values.put(InstalledAppProvider.DataColumns.VERSION_NAME, "Longhorn");
+ values.put(InstalledAppProvider.DataColumns.HASH, "has of test app");
+ values.put(InstalledAppProvider.DataColumns.HASH_TYPE, "fake hash type");
+ values.put(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME, 100000000L);
+ values.put(InstalledAppProvider.DataColumns.SIGNATURE, "000111222333444555666777888999aaabbbcccdddeeefff");
+ contentResolver.insert(InstalledAppProvider.getContentUri(), values);
+
+ Map foundAfter = InstalledAppProvider.Helper.all(RuntimeEnvironment.application);
+ assertEquals(1, foundAfter.size());
+ assertEquals(100000000L, foundAfter.get("org.example.test-app").longValue());
+
+ Cursor cursor = contentResolver.query(InstalledAppProvider.getAppUri("org.example.test-app"), InstalledAppProvider.DataColumns.ALL, null, null, null);
+ assertEquals(cursor.getCount(), 1);
+
+ cursor.moveToFirst();
+ assertEquals("org.example.test-app", cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME)));
+ assertEquals("Test App", cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.APPLICATION_LABEL)));
+ assertEquals(1021, cursor.getInt(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_CODE)));
+ assertEquals("Longhorn", cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_NAME)));
+ assertEquals("has of test app", cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.HASH)));
+ assertEquals("fake hash type", cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.HASH_TYPE)));
+ assertEquals(100000000L, cursor.getLong(cursor.getColumnIndex(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME)));
+ assertEquals("000111222333444555666777888999aaabbbcccdddeeefff", cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.SIGNATURE)));
+
+ cursor.close();
+ }
+
+ @Test
+ public void testInsert() {
+
+ assertResultCount(contentResolver, 0, InstalledAppProvider.getContentUri());
+
+ insertInstalledApp("com.example.com1", 1, "v1");
+ insertInstalledApp("com.example.com2", 2, "v2");
+ insertInstalledApp("com.example.com3", 3, "v3");
+
+ assertResultCount(contentResolver, 3, InstalledAppProvider.getContentUri());
+ assertIsInstalledVersionInDb(contentResolver, "com.example.com1", 1, "v1");
+ assertIsInstalledVersionInDb(contentResolver, "com.example.com2", 2, "v2");
+ assertIsInstalledVersionInDb(contentResolver, "com.example.com3", 3, "v3");
+ }
+
+ @Test
+ public void testUpdate() {
+ insertInstalledApp("com.example.app1", 10, "1.0");
+ insertInstalledApp("com.example.app2", 10, "1.0");
+
+ assertResultCount(contentResolver, 2, InstalledAppProvider.getContentUri());
+ assertIsInstalledVersionInDb(contentResolver, "com.example.app2", 10, "1.0");
+
+ contentResolver.insert(
+ InstalledAppProvider.getContentUri(),
+ createContentValues("com.example.app2", 11, "1.1")
+ );
+
+ assertResultCount(contentResolver, 2, InstalledAppProvider.getContentUri());
+ assertIsInstalledVersionInDb(contentResolver, "com.example.app2", 11, "1.1");
+ }
+
+ /**
+ * We expect this to happen, because we should be using insert() instead as it will
+ * do an insert/replace query.
+ */
+ @Test(expected = UnsupportedOperationException.class)
+ public void testUpdateFails() {
+ contentResolver.update(
+ InstalledAppProvider.getAppUri("com.example.app2"),
+ createContentValues(11, "1.1"),
+ null, null
+ );
+ }
+
+ @Test
+ public void testLastUpdateTime() {
+ String packageName = "com.example.app";
+
+ insertInstalledApp(packageName, 10, "1.0");
+ assertResultCount(contentResolver, 1, InstalledAppProvider.getContentUri());
+ assertIsInstalledVersionInDb(contentResolver, packageName, 10, "1.0");
+
+ Uri uri = InstalledAppProvider.getAppUri(packageName);
+
+ String[] projection = {
+ InstalledAppProvider.DataColumns.PACKAGE_NAME,
+ InstalledAppProvider.DataColumns.LAST_UPDATE_TIME,
+ };
+
+ Cursor cursor = contentResolver.query(uri, projection, null, null, null);
+ assertNotNull(cursor);
+ assertEquals("App \"" + packageName + "\" not installed", 1, cursor.getCount());
+ cursor.moveToFirst();
+ assertEquals(packageName, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME)));
+ long lastUpdateTime = cursor.getLong(cursor.getColumnIndex(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME));
+ assertTrue(lastUpdateTime > 0);
+ assertTrue(lastUpdateTime < System.currentTimeMillis());
+ cursor.close();
+
+ insertInstalledApp(packageName, 11, "1.1");
+ cursor = contentResolver.query(uri, projection, null, null, null);
+ assertNotNull(cursor);
+ assertEquals("App \"" + packageName + "\" not installed", 1, cursor.getCount());
+ cursor.moveToFirst();
+ assertTrue(lastUpdateTime < cursor.getLong(cursor.getColumnIndex(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME)));
+ cursor.close();
+ }
+
+ @Test
+ public void testDelete() {
+
+ insertInstalledApp("com.example.app1", 10, "1.0");
+ insertInstalledApp("com.example.app2", 10, "1.0");
+
+ assertResultCount(contentResolver, 2, InstalledAppProvider.getContentUri());
+
+ contentResolver.delete(InstalledAppProvider.getAppUri("com.example.app1"), null, null);
+
+ assertResultCount(contentResolver, 1, InstalledAppProvider.getContentUri());
+ assertIsInstalledVersionInDb(contentResolver, "com.example.app2", 10, "1.0");
+
+ }
+
+ private ContentValues createContentValues(int versionCode, String versionNumber) {
+ return createContentValues(null, versionCode, versionNumber);
+ }
+
+ private ContentValues createContentValues(String appId, int versionCode, String versionNumber) {
+ ContentValues values = new ContentValues(3);
+ if (appId != null) {
+ values.put(InstalledAppProvider.DataColumns.PACKAGE_NAME, appId);
+ }
+ values.put(InstalledAppProvider.DataColumns.APPLICATION_LABEL, "Mock app: " + appId);
+ values.put(InstalledAppProvider.DataColumns.VERSION_CODE, versionCode);
+ values.put(InstalledAppProvider.DataColumns.VERSION_NAME, versionNumber);
+ values.put(InstalledAppProvider.DataColumns.SIGNATURE, "");
+ values.put(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME, System.currentTimeMillis());
+ values.put(InstalledAppProvider.DataColumns.HASH_TYPE, "sha256");
+ values.put(InstalledAppProvider.DataColumns.HASH, "cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe");
+ return values;
+ }
+
+ private void insertInstalledApp(String appId, int versionCode, String versionNumber) {
+ ContentValues values = createContentValues(appId, versionCode, versionNumber);
+ contentResolver.insert(InstalledAppProvider.getContentUri(), values);
+ }
+}
+
+// https://github.com/robolectric/robolectric/wiki/2.4-to-3.0-Upgrade-Guide
diff --git a/app/src/test/java/org/fdroid/fdroid/data/InstalledAppTestUtils.java b/app/src/test/java/org/fdroid/fdroid/data/InstalledAppTestUtils.java
new file mode 100644
index 000000000..51fd6e905
--- /dev/null
+++ b/app/src/test/java/org/fdroid/fdroid/data/InstalledAppTestUtils.java
@@ -0,0 +1,27 @@
+package org.fdroid.fdroid.data;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+
+public class InstalledAppTestUtils {
+
+ /**
+ * Will tell {@code pm} that we are installing {@code packageName}, and then update the
+ * "installed apps" table in the database.
+ */
+ public static void install(Context context,
+ String packageName,
+ int versionCode, String versionName) {
+ PackageInfo info = new PackageInfo();
+ info.packageName = packageName;
+ info.versionCode = versionCode;
+ info.versionName = versionName;
+ info.applicationInfo = new ApplicationInfo();
+ info.applicationInfo.publicSourceDir = "/tmp/mock-location";
+ String hashType = "sha256";
+ String hash = "00112233445566778899aabbccddeeff";
+ InstalledAppProviderService.insertAppIntoDb(context, packageName, info, hashType, hash);
+ }
+
+}
diff --git a/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java b/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java
new file mode 100644
index 000000000..118adcf65
--- /dev/null
+++ b/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java
@@ -0,0 +1,148 @@
+package org.fdroid.fdroid.data;
+
+import org.fdroid.fdroid.BuildConfig;
+import org.fdroid.fdroid.mock.MockApk;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowContentResolver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.fdroid.fdroid.Assert.assertInvalidUri;
+import static org.fdroid.fdroid.Assert.assertValidUri;
+
+@Config(constants = BuildConfig.class)
+@RunWith(RobolectricGradleTestRunner.class)
+public class ProviderUriTests {
+
+ private ShadowContentResolver resolver;
+
+ @Before
+ public void setup() {
+ resolver = Shadows.shadowOf(RuntimeEnvironment.application.getContentResolver());
+ }
+
+ @After
+ public void teardown() {
+ FDroidProvider.clearDbHelperSingleton();
+ }
+
+ @Test
+ public void invalidInstalledAppProviderUris() {
+ ShadowContentResolver.registerProvider(InstalledAppProvider.getAuthority(), new InstalledAppProvider());
+ assertInvalidUri(resolver, InstalledAppProvider.getAuthority());
+ assertInvalidUri(resolver, "blah");
+ }
+
+ @Test
+ public void validInstalledAppProviderUris() {
+ ShadowContentResolver.registerProvider(InstalledAppProvider.getAuthority(), new InstalledAppProvider());
+ String[] projection = new String[] {InstalledAppProvider.DataColumns._ID};
+ assertValidUri(resolver, InstalledAppProvider.getContentUri(), projection);
+ assertValidUri(resolver, InstalledAppProvider.getAppUri("org.example.app"), projection);
+ assertValidUri(resolver, InstalledAppProvider.getSearchUri("blah"), projection);
+ assertValidUri(resolver, InstalledAppProvider.getSearchUri("\"blah\""), projection);
+ assertValidUri(resolver, InstalledAppProvider.getSearchUri("blah & sneh"), projection);
+ assertValidUri(resolver, InstalledAppProvider.getSearchUri("http://blah.example.com?sneh=\"sneh\""), projection);
+ }
+
+ @Test
+ public void invalidRepoProviderUris() {
+ ShadowContentResolver.registerProvider(RepoProvider.getAuthority(), new RepoProvider());
+ assertInvalidUri(resolver, RepoProvider.getAuthority());
+ assertInvalidUri(resolver, "blah");
+ }
+
+ @Test
+ public void validRepoProviderUris() {
+ ShadowContentResolver.registerProvider(RepoProvider.getAuthority(), new RepoProvider());
+ String[] projection = new String[] {RepoProvider.DataColumns._ID};
+ assertValidUri(resolver, RepoProvider.getContentUri(), projection);
+ assertValidUri(resolver, RepoProvider.getContentUri(10000L), projection);
+ assertValidUri(resolver, RepoProvider.allExceptSwapUri(), projection);
+ }
+
+ @Test
+ public void invalidAppProviderUris() {
+ ShadowContentResolver.registerProvider(AppProvider.getAuthority(), new AppProvider());
+ assertInvalidUri(resolver, AppProvider.getAuthority());
+ assertInvalidUri(resolver, "blah");
+ }
+
+ @Test
+ public void validAppProviderUris() {
+ ShadowContentResolver.registerProvider(AppProvider.getAuthority(), new AppProvider());
+ String[] projection = new String[] {AppProvider.DataColumns._ID};
+ assertValidUri(resolver, AppProvider.getContentUri(), "content://org.fdroid.fdroid.data.AppProvider", projection);
+ assertValidUri(resolver, AppProvider.getSearchUri("'searching!'"), "content://org.fdroid.fdroid.data.AppProvider/search/'searching!'", projection);
+ assertValidUri(resolver, AppProvider.getSearchUri("/"), "content://org.fdroid.fdroid.data.AppProvider/search/%2F", projection);
+ assertValidUri(resolver, AppProvider.getSearchUri(""), "content://org.fdroid.fdroid.data.AppProvider", projection);
+ assertValidUri(resolver, AppProvider.getSearchUri(null), "content://org.fdroid.fdroid.data.AppProvider", projection);
+ assertValidUri(resolver, AppProvider.getNoApksUri(), "content://org.fdroid.fdroid.data.AppProvider/noApks", projection);
+ assertValidUri(resolver, AppProvider.getInstalledUri(), "content://org.fdroid.fdroid.data.AppProvider/installed", projection);
+ assertValidUri(resolver, AppProvider.getCanUpdateUri(), "content://org.fdroid.fdroid.data.AppProvider/canUpdate", projection);
+
+ App app = new App();
+ app.packageName = "org.fdroid.fdroid";
+
+ List apps = new ArrayList<>(1);
+ apps.add(app);
+
+ assertValidUri(resolver, AppProvider.getContentUri(app), "content://org.fdroid.fdroid.data.AppProvider/org.fdroid.fdroid", projection);
+ assertValidUri(resolver, AppProvider.getContentUri(apps), "content://org.fdroid.fdroid.data.AppProvider/apps/org.fdroid.fdroid", projection);
+ assertValidUri(resolver, AppProvider.getContentUri("org.fdroid.fdroid"), "content://org.fdroid.fdroid.data.AppProvider/org.fdroid.fdroid", projection);
+ }
+
+ @Test
+ public void invalidApkProviderUris() {
+ ShadowContentResolver.registerProvider(ApkProvider.getAuthority(), new ApkProvider());
+ assertInvalidUri(resolver, ApkProvider.getAuthority());
+ assertInvalidUri(resolver, "blah");
+ }
+
+ @Test
+ public void validApkProviderUris() {
+ ShadowContentResolver.registerProvider(ApkProvider.getAuthority(), new ApkProvider());
+ String[] projection = new String[] {ApkProvider.DataColumns._ID};
+
+ List apks = new ArrayList<>(10);
+ for (int i = 0; i < 10; i++) {
+ apks.add(new MockApk("com.example." + i, i));
+ }
+
+ assertValidUri(resolver, ApkProvider.getContentUri(), "content://org.fdroid.fdroid.data.ApkProvider", projection);
+ assertValidUri(resolver, ApkProvider.getAppUri("org.fdroid.fdroid"), "content://org.fdroid.fdroid.data.ApkProvider/app/org.fdroid.fdroid", projection);
+ assertValidUri(resolver, ApkProvider.getContentUri(new MockApk("org.fdroid.fdroid", 100)), "content://org.fdroid.fdroid.data.ApkProvider/apk/100/org.fdroid.fdroid", projection);
+ assertValidUri(resolver, ApkProvider.getContentUri(apks), projection);
+ assertValidUri(resolver, ApkProvider.getContentUri("org.fdroid.fdroid", 100), "content://org.fdroid.fdroid.data.ApkProvider/apk/100/org.fdroid.fdroid", projection);
+ assertValidUri(resolver, ApkProvider.getRepoUri(1000), "content://org.fdroid.fdroid.data.ApkProvider/repo/1000", projection);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void invalidApkUrisWithTooManyApks() {
+ String[] projection = ApkProvider.DataColumns.ALL;
+
+ List manyApks = new ArrayList<>(ApkProvider.MAX_APKS_TO_QUERY - 5);
+ for (int i = 0; i < ApkProvider.MAX_APKS_TO_QUERY - 1; i++) {
+ manyApks.add(new MockApk("com.example." + i, i));
+ }
+ assertValidUri(resolver, ApkProvider.getContentUri(manyApks), projection);
+
+ manyApks.add(new MockApk("org.fdroid.fdroid.1", 1));
+ manyApks.add(new MockApk("org.fdroid.fdroid.2", 2));
+
+ // Technically, it is a valid URI, because it doesn't
+ // throw an UnsupportedOperationException. However it
+ // is still not okay (we run out of bindable parameters
+ // in the sqlite query.
+ assertValidUri(resolver, ApkProvider.getContentUri(manyApks), projection);
+ }
+
+}
diff --git a/app/src/androidTest/java/org/fdroid/fdroid/mock/MockApk.java b/app/src/test/java/org/fdroid/fdroid/mock/MockApk.java
similarity index 100%
rename from app/src/androidTest/java/org/fdroid/fdroid/mock/MockApk.java
rename to app/src/test/java/org/fdroid/fdroid/mock/MockApk.java
diff --git a/app/src/androidTest/java/org/fdroid/fdroid/mock/MockApp.java b/app/src/test/java/org/fdroid/fdroid/mock/MockApp.java
similarity index 100%
rename from app/src/androidTest/java/org/fdroid/fdroid/mock/MockApp.java
rename to app/src/test/java/org/fdroid/fdroid/mock/MockApp.java
diff --git a/app/src/androidTest/java/org/fdroid/fdroid/mock/MockRepo.java b/app/src/test/java/org/fdroid/fdroid/mock/MockRepo.java
similarity index 100%
rename from app/src/androidTest/java/org/fdroid/fdroid/mock/MockRepo.java
rename to app/src/test/java/org/fdroid/fdroid/mock/MockRepo.java
diff --git a/app/src/androidTest/assets/README.md b/app/src/test/resources/README.md
similarity index 100%
rename from app/src/androidTest/assets/README.md
rename to app/src/test/resources/README.md
diff --git a/app/src/androidTest/assets/largeRepo.xml b/app/src/test/resources/largeRepo.xml
similarity index 100%
rename from app/src/androidTest/assets/largeRepo.xml
rename to app/src/test/resources/largeRepo.xml
diff --git a/app/src/androidTest/assets/masterKeyIndex.jar b/app/src/test/resources/masterKeyIndex.jar
similarity index 100%
rename from app/src/androidTest/assets/masterKeyIndex.jar
rename to app/src/test/resources/masterKeyIndex.jar
diff --git a/app/src/androidTest/assets/mediumRepo.xml b/app/src/test/resources/mediumRepo.xml
similarity index 100%
rename from app/src/androidTest/assets/mediumRepo.xml
rename to app/src/test/resources/mediumRepo.xml
diff --git a/app/src/androidTest/assets/multiRepo.archive.jar b/app/src/test/resources/multiRepo.archive.jar
similarity index 100%
rename from app/src/androidTest/assets/multiRepo.archive.jar
rename to app/src/test/resources/multiRepo.archive.jar
diff --git a/app/src/androidTest/assets/multiRepo.conflicting.jar b/app/src/test/resources/multiRepo.conflicting.jar
similarity index 100%
rename from app/src/androidTest/assets/multiRepo.conflicting.jar
rename to app/src/test/resources/multiRepo.conflicting.jar
diff --git a/app/src/androidTest/assets/multiRepo.normal.jar b/app/src/test/resources/multiRepo.normal.jar
similarity index 100%
rename from app/src/androidTest/assets/multiRepo.normal.jar
rename to app/src/test/resources/multiRepo.normal.jar
diff --git a/app/src/test/resources/simpleIndex.jar b/app/src/test/resources/simpleIndex.jar
new file mode 100644
index 000000000..1c173ceb3
Binary files /dev/null and b/app/src/test/resources/simpleIndex.jar differ
diff --git a/app/src/androidTest/assets/simpleIndex.xml b/app/src/test/resources/simpleIndex.xml
similarity index 100%
rename from app/src/androidTest/assets/simpleIndex.xml
rename to app/src/test/resources/simpleIndex.xml
diff --git a/app/src/androidTest/assets/simpleIndexWithCorruptedCertificate.jar b/app/src/test/resources/simpleIndexWithCorruptedCertificate.jar
similarity index 100%
rename from app/src/androidTest/assets/simpleIndexWithCorruptedCertificate.jar
rename to app/src/test/resources/simpleIndexWithCorruptedCertificate.jar
diff --git a/app/src/androidTest/assets/simpleIndexWithCorruptedEverything.jar b/app/src/test/resources/simpleIndexWithCorruptedEverything.jar
similarity index 100%
rename from app/src/androidTest/assets/simpleIndexWithCorruptedEverything.jar
rename to app/src/test/resources/simpleIndexWithCorruptedEverything.jar
diff --git a/app/src/androidTest/assets/simpleIndexWithCorruptedManifest.jar b/app/src/test/resources/simpleIndexWithCorruptedManifest.jar
similarity index 100%
rename from app/src/androidTest/assets/simpleIndexWithCorruptedManifest.jar
rename to app/src/test/resources/simpleIndexWithCorruptedManifest.jar
diff --git a/app/src/androidTest/assets/simpleIndexWithCorruptedSignature.jar b/app/src/test/resources/simpleIndexWithCorruptedSignature.jar
similarity index 100%
rename from app/src/androidTest/assets/simpleIndexWithCorruptedSignature.jar
rename to app/src/test/resources/simpleIndexWithCorruptedSignature.jar
diff --git a/app/src/androidTest/assets/simpleIndexWithoutSignature.jar b/app/src/test/resources/simpleIndexWithoutSignature.jar
similarity index 100%
rename from app/src/androidTest/assets/simpleIndexWithoutSignature.jar
rename to app/src/test/resources/simpleIndexWithoutSignature.jar
diff --git a/app/src/androidTest/assets/smallRepo.xml b/app/src/test/resources/smallRepo.xml
similarity index 100%
rename from app/src/androidTest/assets/smallRepo.xml
rename to app/src/test/resources/smallRepo.xml
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index 8bb944a11..735e5d608 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -32,8 +32,8 @@
-
+
diff --git a/config/pmd/rules-main.xml b/config/pmd/rules-main.xml
new file mode 100644
index 000000000..5b33b45d8
--- /dev/null
+++ b/config/pmd/rules-main.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/config/pmd/rules-test.xml b/config/pmd/rules-test.xml
new file mode 100644
index 000000000..0889f5bcf
--- /dev/null
+++ b/config/pmd/rules-test.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/config/pmd/rules.xml b/config/pmd/rules.xml
index 3e4600f4e..e9a161577 100644
--- a/config/pmd/rules.xml
+++ b/config/pmd/rules.xml
@@ -12,7 +12,6 @@
-