blob: 1749606ecb918c161f5b9c74dcec3fde9eee55d6 [file] [log] [blame]
package org.robolectric.bytecode;
import android.R;
import org.robolectric.AndroidManifest;
import org.robolectric.RobolectricBase;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.SdkEnvironment;
import org.robolectric.TestLifecycle;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.DisableStrictI18n;
import org.robolectric.annotation.EnableStrictI18n;
import org.robolectric.impl.ExtendedResponseCache;
import org.robolectric.impl.ResponseSource;
import org.robolectric.internal.DoNotInstrument;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.internal.Instrument;
import org.robolectric.internal.ParallelUniverseInterface;
import org.robolectric.annotation.RealObject;
import org.robolectric.res.ResourceLoader;
import org.robolectric.res.ResourcePath;
import org.robolectric.util.DatabaseConfig;
import org.robolectric.util.I18nException;
import org.robolectric.util.Transcript;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static java.util.Arrays.asList;
public class Setup {
public static final List<String> CLASSES_TO_ALWAYS_DELEGATE = stringify(
RobolectricBase.class,
TestLifecycle.class,
RealObject.class,
ShadowWrangler.class,
AndroidManifest.class,
DatabaseConfig.DatabaseMap.class,
R.class,
org.robolectric.bytecode.InstrumentingClassLoader.class,
org.robolectric.bytecode.AsmInstrumentingClassLoader.class,
SdkEnvironment.class,
RobolectricTestRunner.class,
RobolectricTestRunner.HelperTestRunner.class,
ResourcePath.class,
ResourceLoader.class,
ClassHandler.class,
ClassHandler.Plan.class,
Implements.class,
Implementation.class,
Instrument.class,
DoNotInstrument.class,
Config.class,
EnableStrictI18n.class,
DisableStrictI18n.class,
I18nException.class,
Transcript.class,
org.robolectric.bytecode.DirectObjectMarker.class,
ParallelUniverseInterface.class
);
private static List<String> stringify(Class... classes) {
ArrayList<String> strings = new ArrayList<String>();
for (Class aClass : classes) {
strings.add(aClass.getName());
}
return strings;
}
public List<String> getClassesToDelegateFromRcl() {
//noinspection unchecked
return CLASSES_TO_ALWAYS_DELEGATE;
}
public boolean shouldInstrument(ClassInfo classInfo) {
if (classInfo.isInterface() || classInfo.isAnnotation() || classInfo.hasAnnotation(DoNotInstrument.class)) {
return false;
}
// allow explicit control with @Instrument, mostly for tests
return classInfo.hasAnnotation(Instrument.class) || isFromAndroidSdk(classInfo);
}
public boolean isFromAndroidSdk(ClassInfo classInfo) {
String className = classInfo.getName();
return className.startsWith("android.")
|| className.startsWith("libcore.")
|| className.startsWith("com.android.internal.")
|| className.startsWith("com.google.android.maps.")
|| className.startsWith("org.apache.http.impl.client.DefaultRequestDirector");
}
public boolean shouldAcquire(String name) {
// the org.robolectric.res package lives in the base classloader, but not its tests; yuck.
int lastDot = name.lastIndexOf('.');
String pkgName = name.substring(0, lastDot == -1 ? 0 : lastDot);
if (pkgName.equals("org.robolectric.res")) {
return name.contains("Test");
}
if (name.matches("com\\.android\\.internal\\.R(\\$.*)?")) return true;
// Android SDK code almost universally refers to com.android.internal.R, except
// when refering to android.R.stylable, as in HorizontalScrollView. arghgh.
// See https://github.com/robolectric/robolectric/issues/521
if (name.equals("android.R$styleable")) return true;
return !(
name.matches(".*\\.R(|\\$[a-z]+)$")
|| CLASSES_TO_ALWAYS_DELEGATE.contains(name)
|| name.startsWith("java.")
|| name.startsWith("javax.")
|| name.startsWith("sun.")
|| name.startsWith("com.sun.")
|| name.startsWith("org.w3c.")
|| name.startsWith("org.xml.")
|| name.startsWith("org.junit")
|| name.startsWith("org.hamcrest")
|| name.startsWith("org.specs2") // allows for android projects with mixed scala\java tests to be
|| name.startsWith("scala.") // run with Maven Surefire (see the RoboSpecs project on github)
|| name.startsWith("org.sqlite.") // ugh, we're barfing while loading org.sqlite now for some reason?!? todo: still?
);
}
public Set<MethodRef> methodsToIntercept() {
return Collections.unmodifiableSet(new HashSet<MethodRef>(asList(
new MethodRef(LinkedHashMap.class, "eldest"),
new MethodRef(System.class, "loadLibrary"),
new MethodRef("android.os.StrictMode", "trackActivity"),
new MethodRef("com.android.i18n.phonenumbers.Phonenumber$PhoneNumber", "*"),
new MethodRef("com.android.i18n.phonenumbers.PhoneNumberUtil", "*"),
new MethodRef("dalvik.system.CloseGuard", "get"),
new MethodRef("java.lang.AutoCloseable", "*"),
new MethodRef("android.util.LocaleUtil", "getLayoutDirectionFromLocale"),
new MethodRef("com.android.internal.policy.PolicyManager", "*"),
new MethodRef("android.view.CompatibilityInfoHolder", "*"),
new MethodRef("android.content.res.CompatibilityInfo", "*"),
new MethodRef("android.view.FallbackEventHandler", "*"),
new MethodRef("android.view.IWindowSession", "*")
)));
}
/**
* Map from a requested class to an alternate stand-in, or not.
*
* @return
*/
public Map<String, String> classNameTranslations() {
Map<String, String> map = new HashMap<String, String>();
map.put("com.android.i18n.phonenumbers.NumberParseException", Exception.class.getName());
map.put("com.android.i18n.phonenumbers.PhoneNumberUtil", FakeClass.class.getName());
map.put("com.android.i18n.phonenumbers.PhoneNumberUtil$PhoneNumberFormat", FakeClass.FakeInnerClass.class.getName());
map.put("com.android.i18n.phonenumbers.Phonenumber$PhoneNumber", FakeClass.class.getName());
map.put("dalvik.system.CloseGuard", Object.class.getName());
map.put("java.lang.AutoCloseable", Object.class.getName());
map.put("java.net.ExtendedResponseCache", ExtendedResponseCache.class.getName());
map.put("java.net.ResponseSource", ResponseSource.class.getName());
return map;
}
public static class FakeClass {
public static class FakeInnerClass {}
}
public boolean containsStubs(ClassInfo classInfo) {
return classInfo.getName().startsWith("com.google.android.maps.");
}
public static class MethodRef {
public final String className;
public final String methodName;
public MethodRef(Class<?> clazz, String methodName) {
this(clazz.getName(), methodName);
}
public MethodRef(String className, String methodName) {
this.className = className;
this.methodName = methodName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MethodRef methodRef = (MethodRef) o;
if (!className.equals(methodRef.className)) return false;
if (!methodName.equals(methodRef.methodName)) return false;
return true;
}
@Override
public int hashCode() {
int result = className.hashCode();
result = 31 * result + methodName.hashCode();
return result;
}
@Override
public String toString() {
return "MethodRef{" +
"className='" + className + '\'' +
", methodName='" + methodName + '\'' +
'}';
}
}
}