Merge branch 'robolectric' into 'master'

Port most tests to JVM via Robolectric library

Fixes #607.

This ports all but one of the tests from `app/src/androidTest` to `app/src/test` to be run on the JVM.

I would've liked to port the final one, but it must be run on Android because we are testing the ability of an Android OS to perform symlinks. Also, it is not a bad thing in itself to have tests run on an emulator, just that those which _can_ be run on the host JVM should be. In the future, there will no doubt be other tests which are required to run on the JVM. At the very least, we should be able to run tests on faster emulators now because we are not constrained by the failing provider tests.

This branch required only very minimal changes to the client code, but resulted in the removal of a whole bunch of crappy mocking code that I had to add in order to support testing of the content providers (i.e. navigating around all of the final/hidden/etc apis in Android).

See merge request !327
This commit is contained in:
Daniel Martí 2016-06-09 11:02:11 +00:00
commit 345d735d8f
62 changed files with 2190 additions and 2897 deletions

View File

@ -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

View File

@ -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.
* <p>
* This test case also sets up the following mock objects:
* </p>
* <ul>
* <li>
* 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.
* </li>
* <li>
* 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.
* </li>
* <li>
* An instance of the provider under test, running in an {@link IsolatedContext}.
* </li>
* </ul>
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* For more information on content provider testing, please see
* <a href="{@docRoot}tools/testing/contentprovider_testing.html">Content Provider Testing</a>.
*/
public abstract class ProviderTestCase2MockContext<T extends ContentProvider> extends AndroidTestCase {
Class<T> 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<T> 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.
* <p>
* 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.
* </p>
*
* @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.
* <p>
* 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;
}
/**
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* This is a convenience method for creating a "mock" provider that can contain test data.
* </p>
*
* @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
* <a href="http://www.sqlite.org/sqlite.html">sqlite3</a> tool's <code>.dump</code> command.
* @return ContentResolver A new {@link MockContentResolver} linked to the provider
*
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static <T extends ContentProvider> ContentResolver newResolverWithContentProviderFromSql(
Context targetContext, String filenamePrefix, Class<T> 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;
}
}

View File

@ -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;
}
}

View File

@ -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 "";
}
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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<PackageInfo> getInstalledPackages(int flags) {
return new ArrayList<>();
}
}

View File

@ -1,12 +0,0 @@
package mock;
import android.test.mock.MockResources;
public class MockEmptyResources extends MockResources {
@Override
public String getString(int id) {
return "";
}
}

View File

@ -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;
}
}
}

View File

@ -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<PackageInfo> info = new ArrayList<>();
@Override
public List<PackageInfo> 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<PackageInfo> it = info.iterator(); it.hasNext();) {
PackageInfo info = it.next();
if (info.packageName.equals(id)) {
it.remove();
return;
}
}
}
}

View File

@ -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;
}
}

View File

@ -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<Repo> 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<Repo> repos = RepoProvider.Helper.all(context);
assertEquals("Repos", 3, repos.size());
assertApp2048();
assertAppAdaway();
assertAppAdbWireless();
assertAppIcsImport();
}
private void assertApp(String packageName, int[] versionCodes) {
List<Apk> 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<Repo> allRepos) {
Repo repo = findRepo(REPO_MAIN, allRepos);
List<Apk> 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<Repo> allRepos) {
Repo repo = findRepo(REPO_ARCHIVE, allRepos);
List<Apk> 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<Repo> allRepos) {
Repo repo = findRepo(REPO_CONFLICTING, allRepos);
List<Apk> 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<Repo> 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<Apk> 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;
}
}

View File

@ -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!
}
}
}

View File

@ -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 <T extends Comparable> void assertContainsOnly(List<T> actualList, T[] expectedArray) {
List<T> expectedList = new ArrayList<>(expectedArray.length);
Collections.addAll(expectedList, expectedArray);
assertContainsOnly(actualList, expectedList);
}
public static <T extends Comparable> void assertContainsOnly(T[] actualArray, List<T> expectedList) {
List<T> actualList = new ArrayList<>(actualArray.length);
Collections.addAll(actualList, actualArray);
assertContainsOnly(actualList, expectedList);
}
public static <T extends Comparable> void assertContainsOnly(T[] actualArray, T[] expectedArray) {
List<T> expectedList = new ArrayList<>(expectedArray.length);
Collections.addAll(expectedList, expectedArray);
assertContainsOnly(actualArray, expectedList);
}
public static <T> String listToString(List<T> list) {
String string = "[";
for (int i = 0; i < list.size(); i++) {
if (i > 0) {
string += ", ";
}
string += "'" + list.get(i) + "'";
}
string += "]";
return string;
}
public static <T extends Comparable> void assertContainsOnly(List<T> actualList, List<T> 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<ApkProvider> providerTest, String id, int versionCode) {
return insertApk(providerTest, id, versionCode, new ContentValues());
}
public static Uri insertApk(FDroidProviderTest<ApkProvider> 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;
}
}

View File

@ -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<Apk> 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<Apk> 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<Apk> fdroidApks = ApkProvider.Helper.findByPackageName(getMockContext(), "org.fdroid.fdroid");
assertResultCount(7, fdroidApks);
assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
List<Apk> exampleApks = ApkProvider.Helper.findByPackageName(getMockContext(), "org.example");
assertResultCount(9, exampleApks);
assertBelongsToApp(exampleApks, "org.example");
List<Apk> exampleApks2 = ApkProvider.Helper.findByPackageName(getMockContext(), "com.example");
assertResultCount(3, exampleApks2);
assertBelongsToApp(exampleApks2, "com.example");
List<Apk> 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);
}
}

View File

@ -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<Apk> apks) {
return ApkProvider.getContentUri(apks);
}
}
public void testUris() {
assertInvalidUri(ApkProvider.getAuthority());
assertInvalidUri(RepoProvider.getContentUri());
List<Apk> 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<Apk> 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<Apk> 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<Apk> all = ApkProvider.Helper.findByRepo(getSwappableContext(), new MockRepo(10), ApkProvider.DataColumns.ALL);
List<String> actualIds = new ArrayList<>();
for (Apk apk : all) {
actualIds.add(apk.packageName);
}
TestUtils.assertContainsOnly(actualIds, expectedIds);
List<Apk> toDelete = new ArrayList<>(3);
toDelete.add(two);
toDelete.add(three);
toDelete.add(four);
ApkProvider.Helper.deleteApks(getSwappableContext(), toDelete);
assertTotalApkCount(2);
List<Apk> allRemaining = ApkProvider.Helper.findByRepo(getSwappableContext(), new MockRepo(10), ApkProvider.DataColumns.ALL);
List<String> 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);
}
}

View File

@ -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<AppProvider> {
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<App> 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<String> 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<App> 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<App> actualApps, String[] expectedIds) {
List<String> 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<String> 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<String> 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<String> 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);
}
}

View File

@ -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<ApkProvider> {
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<Apk> 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<Apk> 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);
}
}

View File

@ -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<T extends FDroidProvider> extends ProviderTestCase2MockContext<T> {
private FDroidProvider[] allProviders = {
new AppProvider(),
new RepoProvider(),
new ApkProvider(),
new InstalledAppProvider(),
};
private MockContextSwappableComponents swappableContext;
public FDroidProviderTest(Class<T> 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();
}
}

View File

@ -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<InstalledAppProvider>, 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<InstalledAppProvider> {
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");
}
*/
}

View File

@ -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<InstalledAppProvider> {
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);
}
}

View File

@ -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.
*/

View File

@ -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);

View File

@ -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<Apk> apks) {
static Uri getContentUri(List<Apk> apks) {
return getContentUri().buildUpon()
.appendPath(PATH_APKS)
.appendPath(buildApkString(apks))

View File

@ -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);

View File

@ -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<Repo> 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();
}
}
}

View File

@ -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 <T extends Comparable> void assertContainsOnly(List<T> actualList, T[] expectedArray) {
List<T> expectedList = new ArrayList<>(expectedArray.length);
Collections.addAll(expectedList, expectedArray);
assertContainsOnly(actualList, expectedList);
}
public static <T extends Comparable> void assertContainsOnly(T[] actualArray, List<T> expectedList) {
List<T> actualList = new ArrayList<>(actualArray.length);
Collections.addAll(actualList, actualArray);
assertContainsOnly(actualList, expectedList);
}
public static <T extends Comparable> void assertContainsOnly(T[] actualArray, T[] expectedArray) {
List<T> expectedList = new ArrayList<>(expectedArray.length);
Collections.addAll(expectedList, expectedArray);
assertContainsOnly(actualArray, expectedList);
}
public static <T> String listToString(List<T> list) {
String string = "[";
for (int i = 0; i < list.size(); i++) {
if (i > 0) {
string += ", ";
}
string += "'" + list.get(i) + "'";
}
string += "]";
return string;
}
public static <T extends Comparable> void assertContainsOnly(List<T> actualList, List<T> 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);
}
}

View File

@ -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<Apk> 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<Repo> 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<Apk> 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;
}
}

View File

@ -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<Repo> 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<Repo> allRepos) {
Repo repo = findRepo(REPO_MAIN, allRepos);
List<Apk> 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<Repo> allRepos) {
Repo repo = findRepo(REPO_ARCHIVE, allRepos);
List<Apk> 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<Repo> allRepos) {
Repo repo = findRepo(REPO_CONFLICTING, allRepos);
List<Apk> 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});
}
}

View File

@ -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;

View File

@ -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 {
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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<Apk> all = ApkProvider.Helper.findByRepo(context, new MockRepo(10), ApkProvider.DataColumns.ALL);
List<String> actualIds = new ArrayList<>();
for (Apk apk : all) {
actualIds.add(apk.packageName);
}
assertContainsOnly(actualIds, expectedIds);
List<Apk> toDelete = new ArrayList<>(3);
toDelete.add(two);
toDelete.add(three);
toDelete.add(four);
ApkProvider.Helper.deleteApks(context, toDelete);
assertTotalApkCount(2);
List<Apk> allRemaining = ApkProvider.Helper.findByRepo(context, new MockRepo(10), ApkProvider.DataColumns.ALL);
List<String> 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<Apk> 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<Apk> 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<Apk> fdroidApks = ApkProvider.Helper.findByPackageName(context, "org.fdroid.fdroid");
assertResultCount(7, fdroidApks);
assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
List<Apk> exampleApks = ApkProvider.Helper.findByPackageName(context, "org.example");
assertResultCount(9, exampleApks);
assertBelongsToApp(exampleApks, "org.example");
List<Apk> exampleApks2 = ApkProvider.Helper.findByPackageName(context, "com.example");
assertResultCount(3, exampleApks2);
assertBelongsToApp(exampleApks2, "com.example");
List<Apk> 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<Apk> 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<Apk> 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);
}
}

View File

@ -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<String> 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<App> 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<App> actualApps, String[] expectedIds) {
List<String> 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<String> 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<String> 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<String> 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);
}
}

View File

@ -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();
}
}

View File

@ -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<String, Long> 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<String, Long> 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

View File

@ -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);
}
}

View File

@ -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<App> 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<Apk> 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<Apk> 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);
}
}

Binary file not shown.

View File

@ -32,8 +32,8 @@
<module name="AvoidStarImport" />
<module name="AvoidStaticImport">
<property name="excludes"
value="org.assertj.core.api.Assertions.*, org.junit.Assert.*, org.junit.Assume.*, org.junit.internal.matchers.ThrowableMessageMatcher.*, org.hamcrest.CoreMatchers.*, org.hamcrest.Matchers.*, org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.*, org.springframework.boot.configurationprocessor.TestCompiler.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.Matchers.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*, org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*, org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*, org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo" />
<property name="excludes"
value="org.fdroid.fdroid.Assert.*, org.assertj.core.api.Assertions.*, org.junit.Assert.*, org.junit.Assume.*, org.junit.internal.matchers.ThrowableMessageMatcher.*, org.hamcrest.CoreMatchers.*, org.hamcrest.Matchers.*, org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.*, org.springframework.boot.configurationprocessor.TestCompiler.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.Matchers.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*, org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*, org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*, org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo" />
</module>
<module name="RedundantImport" />
<module name="UnusedImports" />

View File

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<ruleset name="Custom ruleset (for main code)"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
<rule ref="rulesets/java/imports.xml" />
</ruleset>

11
config/pmd/rules-test.xml Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<ruleset name="Custom ruleset (for tests)"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
<rule ref="rulesets/java/imports.xml">
<exclude name="TooManyStaticImports" /><!-- Static imports are commonly used for JUnit tests -->
</rule>
</ruleset>

View File

@ -12,7 +12,6 @@
<rule ref="rulesets/java/android.xml"/>
<rule ref="rulesets/java/clone.xml"/>
<rule ref="rulesets/java/finalizers.xml"/>
<rule ref="rulesets/java/imports.xml"/>
<rule ref="rulesets/java/migrating.xml"/>
<rule ref="rulesets/java/unnecessary.xml">
<exclude name="UselessParentheses"/> <!--Too nitpicky-->