blob: e5eb2731b7b064f881f23eac7c17fc36719376e8 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.base.test;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.support.v4.content.ContextCompat;
import org.chromium.android.support.PackageManagerWrapper;
import org.chromium.base.Log;
import org.chromium.base.annotations.MainDex;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Comparator;
/**
* Functionality common to the JUnit3 and JUnit4 runners.
*/
@MainDex
class BaseChromiumRunnerCommon {
private static final String TAG = "base_test";
/**
* A ContextWrapper that allows multidex test APKs to extract secondary dexes into
* the APK under test's data directory.
*/
@MainDex
static class MultiDexContextWrapper extends ContextWrapper {
private Context mAppContext;
MultiDexContextWrapper(Context instrContext, Context appContext) {
super(instrContext);
mAppContext = appContext;
}
@Override
public File getFilesDir() {
return mAppContext.getFilesDir();
}
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
return mAppContext.getSharedPreferences(name, mode);
}
@Override
public PackageManager getPackageManager() {
return new PackageManagerWrapper(super.getPackageManager()) {
@Override
public ApplicationInfo getApplicationInfo(String packageName, int flags) {
try {
ApplicationInfo ai = super.getApplicationInfo(packageName, flags);
if (packageName.equals(getPackageName())) {
File dataDir = new File(
ContextCompat.getCodeCacheDir(mAppContext), "test-multidex");
if (!dataDir.exists() && !dataDir.mkdirs()) {
throw new IOException(String.format(
"Unable to create test multidex directory \"%s\"",
dataDir.getPath()));
}
ai.dataDir = dataDir.getPath();
}
return ai;
} catch (Exception e) {
Log.e(TAG, "Failed to get application info for %s", packageName, e);
}
return null;
}
};
}
}
/**
* Ensure all test dex entries precede app dex entries.
*
* @param cl ClassLoader to modify. Assumed to be a derivative of
* {@link dalvik.system.BaseDexClassLoader}. If this isn't
* the case, reordering will fail.
*/
static void reorderDexPathElements(ClassLoader cl, Context context, Context targetContext) {
try {
Log.i(TAG,
"Reordering dex files. If you're building a multidex test APK and see a "
+ "class resolving to an unexpected implementation, this may be why.");
Field pathListField = findField(cl, "pathList");
Object dexPathList = pathListField.get(cl);
Field dexElementsField = findField(dexPathList, "dexElements");
Object[] dexElementsList = (Object[]) dexElementsField.get(dexPathList);
Arrays.sort(dexElementsList,
new DexListReorderingComparator(
context.getPackageName(), targetContext.getPackageName()));
dexElementsField.set(dexPathList, dexElementsList);
} catch (Exception e) {
Log.e(TAG, "Failed to reorder dex elements for testing.", e);
}
}
/**
* Comparator for sorting dex list entries.
*
* Using this to sort a list of dex list entries will result in the following order:
* - Strings that contain neither the test package nor the app package in lexicographical
* order.
* - Strings that contain the test package in lexicographical order.
* - Strings that contain the app package but not the test package in lexicographical order.
*/
private static class DexListReorderingComparator implements Comparator<Object>, Serializable {
private String mTestPackage;
private String mAppPackage;
public DexListReorderingComparator(String testPackage, String appPackage) {
mTestPackage = testPackage;
mAppPackage = appPackage;
}
@Override
public int compare(Object o1, Object o2) {
String s1 = o1.toString();
String s2 = o2.toString();
if (s1.contains(mTestPackage)) {
if (!s2.contains(mTestPackage)) {
if (s2.contains(mAppPackage)) {
return -1;
} else {
return 1;
}
}
} else if (s1.contains(mAppPackage)) {
if (s2.contains(mTestPackage)) {
return 1;
} else if (!s2.contains(mAppPackage)) {
return 1;
}
} else if (s2.contains(mTestPackage) || s2.contains(mAppPackage)) {
return -1;
}
return s1.compareTo(s2);
}
}
private static Field findField(Object instance, String name) throws NoSuchFieldException {
for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
try {
Field f = clazz.getDeclaredField(name);
f.setAccessible(true);
return f;
} catch (NoSuchFieldException e) {
}
}
throw new NoSuchFieldException(
"Unable to find field " + name + " in " + instance.getClass());
}
}