blob: e6912c4ae4740ebb3236312a888e350ad1be2a79 [file] [log] [blame]
/*
* Copyright (C) 2011 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 com.google.dexmaker.stock;
import com.google.dexmaker.DexMakerTest;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Arrays;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
public class ProxyBuilderTest extends TestCase {
private FakeInvocationHandler fakeHandler = new FakeInvocationHandler();
private File versionedDxDir = new File(DexMakerTest.getDataDirectory(), "v1");
public void setUp() throws Exception {
super.setUp();
versionedDxDir.mkdirs();
clearVersionedDxDir();
getGeneratedProxyClasses().clear();
}
public void tearDown() throws Exception {
getGeneratedProxyClasses().clear();
clearVersionedDxDir();
super.tearDown();
}
private void clearVersionedDxDir() {
for (File f : versionedDxDir.listFiles()) {
f.delete();
}
}
public static class SimpleClass {
public String simpleMethod() {
throw new AssertionFailedError();
}
}
public void testExampleOperation() throws Throwable {
fakeHandler.setFakeResult("expected");
SimpleClass proxy = proxyFor(SimpleClass.class).build();
assertEquals("expected", proxy.simpleMethod());
assertEquals(2, versionedDxDir.listFiles().length);
}
public void testExampleOperation_DexMakerCaching() throws Throwable {
fakeHandler.setFakeResult("expected");
SimpleClass proxy = proxyFor(SimpleClass.class).build();
assertEquals(2, versionedDxDir.listFiles().length);
assertEquals("expected", proxy.simpleMethod());
// Force ProxyBuilder to create a DexMaker generator and call DexMaker.generateAndLoad().
getGeneratedProxyClasses().clear();
proxy = proxyFor(SimpleClass.class).build();
assertEquals(2, versionedDxDir.listFiles().length);
assertEquals("expected", proxy.simpleMethod());
}
public static class ConstructorTakesArguments {
private final String argument;
public ConstructorTakesArguments(String arg) {
argument = arg;
}
public String method() {
throw new AssertionFailedError();
}
}
public void testConstruction_SucceedsIfCorrectArgumentsProvided() throws Throwable {
ConstructorTakesArguments proxy = proxyFor(ConstructorTakesArguments.class)
.constructorArgTypes(String.class)
.constructorArgValues("hello")
.build();
assertEquals("hello", proxy.argument);
proxy.method();
}
public void testConstruction_FailsWithWrongNumberOfArguments() throws Throwable {
try {
proxyFor(ConstructorTakesArguments.class).build();
fail();
} catch (IllegalArgumentException expected) {}
}
public void testClassIsNotAccessbile_FailsWithUnsupportedOperationException() throws Exception {
class MethodVisibilityClass {
}
try {
proxyFor(MethodVisibilityClass.class).build();
fail();
} catch (UnsupportedOperationException expected) {}
}
private static class PrivateVisibilityClass {
}
public void testPrivateClass_FailsWithUnsupportedOperationException() throws Exception {
try {
proxyFor(PrivateVisibilityClass.class).build();
fail();
} catch (UnsupportedOperationException expected) {}
}
protected static class ProtectedVisibilityClass {
public String foo() {
throw new AssertionFailedError();
}
}
public void testProtectedVisibility_WorksFine() throws Exception {
assertEquals("fake result", proxyFor(ProtectedVisibilityClass.class).build().foo());
}
public static class HasFinalMethod {
public String nonFinalMethod() {
return "non-final method";
}
public final String finalMethod() {
return "final method";
}
}
public void testCanProxyClassesWithFinalMethods_WillNotCallTheFinalMethod() throws Throwable {
HasFinalMethod proxy = proxyFor(HasFinalMethod.class).build();
assertEquals("final method", proxy.finalMethod());
assertEquals("fake result", proxy.nonFinalMethod());
}
public static class HasPrivateMethod {
private String result() {
return "expected";
}
}
public void testProxyingPrivateMethods_NotIntercepted() throws Throwable {
assertEquals("expected", proxyFor(HasPrivateMethod.class).build().result());
}
public static class HasPackagePrivateMethod {
String result() {
throw new AssertionFailedError();
}
}
public void testProxyingPackagePrivateMethods_AreIntercepted() throws Throwable {
assertEquals("fake result", proxyFor(HasPackagePrivateMethod.class).build().result());
}
public static class HasProtectedMethod {
protected String result() {
throw new AssertionFailedError();
}
}
public void testProxyingProtectedMethods_AreIntercepted() throws Throwable {
assertEquals("fake result", proxyFor(HasProtectedMethod.class).build().result());
}
public static class HasVoidMethod {
public void dangerousMethod() {
fail();
}
}
public void testVoidMethod_ShouldNotThrowRuntimeException() throws Throwable {
proxyFor(HasVoidMethod.class).build().dangerousMethod();
}
public void testObjectMethodsAreAlsoProxied() throws Throwable {
Object proxy = proxyFor(Object.class).build();
fakeHandler.setFakeResult("mystring");
assertEquals("mystring", proxy.toString());
fakeHandler.setFakeResult(-1);
assertEquals(-1, proxy.hashCode());
fakeHandler.setFakeResult(false);
assertEquals(false, proxy.equals(proxy));
}
public static class AllReturnTypes {
public boolean getBoolean() { return true; }
public int getInt() { return 1; }
public byte getByte() { return 2; }
public long getLong() { return 3L; }
public short getShort() { return 4; }
public float getFloat() { return 5f; }
public double getDouble() { return 6.0; }
public char getChar() { return 'c'; }
public int[] getIntArray() { return new int[] { 8, 9 }; }
public String[] getStringArray() { return new String[] { "d", "e" }; }
}
public void testAllReturnTypes() throws Throwable {
AllReturnTypes proxy = proxyFor(AllReturnTypes.class).build();
fakeHandler.setFakeResult(false);
assertEquals(false, proxy.getBoolean());
fakeHandler.setFakeResult(8);
assertEquals(8, proxy.getInt());
fakeHandler.setFakeResult((byte) 9);
assertEquals(9, proxy.getByte());
fakeHandler.setFakeResult(10L);
assertEquals(10, proxy.getLong());
fakeHandler.setFakeResult((short) 11);
assertEquals(11, proxy.getShort());
fakeHandler.setFakeResult(12f);
assertEquals(12f, proxy.getFloat());
fakeHandler.setFakeResult(13.0);
assertEquals(13.0, proxy.getDouble());
fakeHandler.setFakeResult('z');
assertEquals('z', proxy.getChar());
fakeHandler.setFakeResult(new int[] { -1, -2 });
assertEquals("[-1, -2]", Arrays.toString(proxy.getIntArray()));
fakeHandler.setFakeResult(new String[] { "x", "y" });
assertEquals("[x, y]", Arrays.toString(proxy.getStringArray()));
}
public static class PassThroughAllTypes {
public boolean getBoolean(boolean input) { return input; }
public int getInt(int input) { return input; }
public byte getByte(byte input) { return input; }
public long getLong(long input) { return input; }
public short getShort(short input) { return input; }
public float getFloat(float input) { return input; }
public double getDouble(double input) { return input; }
public char getChar(char input) { return input; }
public String getString(String input) { return input; }
public Object getObject(Object input) { return input; }
public void getNothing() {}
}
public static class InvokeSuperHandler implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return ProxyBuilder.callSuper(proxy, method, args);
}
}
public void testPassThroughWorksForAllTypes() throws Exception {
PassThroughAllTypes proxy = proxyFor(PassThroughAllTypes.class)
.handler(new InvokeSuperHandler())
.build();
assertEquals(false, proxy.getBoolean(false));
assertEquals(true, proxy.getBoolean(true));
assertEquals(0, proxy.getInt(0));
assertEquals(1, proxy.getInt(1));
assertEquals((byte) 2, proxy.getByte((byte) 2));
assertEquals((byte) 3, proxy.getByte((byte) 3));
assertEquals(4L, proxy.getLong(4L));
assertEquals(5L, proxy.getLong(5L));
assertEquals((short) 6, proxy.getShort((short) 6));
assertEquals((short) 7, proxy.getShort((short) 7));
assertEquals(8f, proxy.getFloat(8f));
assertEquals(9f, proxy.getFloat(9f));
assertEquals(10.0, proxy.getDouble(10.0));
assertEquals(11.0, proxy.getDouble(11.0));
assertEquals('a', proxy.getChar('a'));
assertEquals('b', proxy.getChar('b'));
assertEquals("asdf", proxy.getString("asdf"));
assertEquals("qwer", proxy.getString("qwer"));
assertEquals(null, proxy.getString(null));
Object a = new Object();
assertEquals(a, proxy.getObject(a));
assertEquals(null, proxy.getObject(null));
proxy.getNothing();
}
public static class ExtendsAllReturnTypes extends AllReturnTypes {
public int example() { return 0; }
}
public void testProxyWorksForSuperclassMethodsAlso() throws Throwable {
ExtendsAllReturnTypes proxy = proxyFor(ExtendsAllReturnTypes.class).build();
fakeHandler.setFakeResult(99);
assertEquals(99, proxy.example());
assertEquals(99, proxy.getInt());
assertEquals(99, proxy.hashCode());
}
public static class HasOddParams {
public long method(int first, Integer second) {
throw new AssertionFailedError();
}
}
public void testMixingBoxedAndUnboxedParams() throws Throwable {
HasOddParams proxy = proxyFor(HasOddParams.class).build();
fakeHandler.setFakeResult(99L);
assertEquals(99L, proxy.method(1, Integer.valueOf(2)));
}
public static class SingleInt {
public String getString(int value) {
throw new AssertionFailedError();
}
}
public void testSinglePrimitiveParameter() throws Throwable {
InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return "asdf" + ((Integer) args[0]).intValue();
}
};
assertEquals("asdf1", proxyFor(SingleInt.class).handler(handler).build().getString(1));
}
public static class TwoConstructors {
private final String string;
public TwoConstructors() {
string = "no-arg";
}
public TwoConstructors(boolean unused) {
string = "one-arg";
}
}
public void testNoConstructorArguments_CallsNoArgConstructor() throws Throwable {
TwoConstructors twoConstructors = proxyFor(TwoConstructors.class).build();
assertEquals("no-arg", twoConstructors.string);
}
public void testWithoutInvocationHandler_ThrowsIllegalArgumentException() throws Throwable {
try {
ProxyBuilder.forClass(TwoConstructors.class)
.dexCache(DexMakerTest.getDataDirectory())
.build();
fail();
} catch (IllegalArgumentException expected) {}
}
public static class HardToConstructCorrectly {
public HardToConstructCorrectly() { fail(); }
public HardToConstructCorrectly(Runnable ignored) { fail(); }
public HardToConstructCorrectly(Exception ignored) { fail(); }
public HardToConstructCorrectly(Boolean ignored) { /* safe */ }
public HardToConstructCorrectly(Integer ignored) { fail(); }
}
public void testHardToConstruct_WorksIfYouSpecifyTheConstructorCorrectly() throws Throwable {
proxyFor(HardToConstructCorrectly.class)
.constructorArgTypes(Boolean.class)
.constructorArgValues(true)
.build();
}
public void testHardToConstruct_EvenWorksWhenArgsAreAmbiguous() throws Throwable {
proxyFor(HardToConstructCorrectly.class)
.constructorArgTypes(Boolean.class)
.constructorArgValues(new Object[] { null })
.build();
}
public void testHardToConstruct_DoesNotInferTypesFromValues() throws Throwable {
try {
proxyFor(HardToConstructCorrectly.class)
.constructorArgValues(true)
.build();
fail();
} catch (IllegalArgumentException expected) {}
}
public void testDefaultProxyHasSuperMethodToAccessOriginal() throws Exception {
Object objectProxy = proxyFor(Object.class).build();
assertNotNull(objectProxy.getClass().getMethod("super$hashCode$int"));
}
public static class PrintsOddAndValue {
public String method(int value) {
return "odd " + value;
}
}
public void testSometimesDelegateToSuper() throws Exception {
InvocationHandler delegatesOddValues = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("method")) {
int intValue = ((Integer) args[0]).intValue();
if (intValue % 2 == 0) {
return "even " + intValue;
}
}
return ProxyBuilder.callSuper(proxy, method, args);
}
};
PrintsOddAndValue proxy = proxyFor(PrintsOddAndValue.class)
.handler(delegatesOddValues)
.build();
assertEquals("even 0", proxy.method(0));
assertEquals("odd 1", proxy.method(1));
assertEquals("even 2", proxy.method(2));
assertEquals("odd 3", proxy.method(3));
}
public void testCallSuperThrows() throws Exception {
InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
return ProxyBuilder.callSuper(o, method, objects);
}
};
FooThrows fooThrows = proxyFor(FooThrows.class)
.handler(handler)
.build();
try {
fooThrows.foo();
fail();
} catch (IllegalStateException expected) {
assertEquals("boom!", expected.getMessage());
}
}
public static class FooThrows {
public void foo() {
throw new IllegalStateException("boom!");
}
}
public static class DoubleReturn {
public double getValue() {
return 2.0;
}
}
public void testUnboxedResult() throws Exception {
fakeHandler.fakeResult = 2.0;
assertEquals(2.0, proxyFor(DoubleReturn.class).build().getValue());
}
public static void staticMethod() {
}
public void testDoesNotOverrideStaticMethods() throws Exception {
// Method should exist on this test class itself.
ProxyBuilderTest.class.getDeclaredMethod("staticMethod");
// Method should not exist on the subclass.
try {
proxyFor(ProxyBuilderTest.class).build().getClass().getDeclaredMethod("staticMethod");
fail();
} catch (NoSuchMethodException expected) {}
}
public void testIllegalCacheDirectory() throws Exception {
try {
proxyFor(ProxyForIllegalCacheDirectory.class)
.dexCache(new File("/poop/"))
.build();
fail();
} catch (IOException expected) {
}
}
public static class ProxyForIllegalCacheDirectory {
}
public void testInvalidConstructorSpecification() throws Exception {
try {
proxyFor(Object.class)
.constructorArgTypes(String.class, Boolean.class)
.constructorArgValues("asdf", true)
.build();
fail();
} catch (IllegalArgumentException expected) {}
}
public static abstract class AbstractClass {
public abstract Object getValue();
}
public void testAbstractClassBehaviour() throws Exception {
assertEquals("fake result", proxyFor(AbstractClass.class).build().getValue());
}
public static class CtorHasDeclaredException {
public CtorHasDeclaredException() throws IOException {
throw new IOException();
}
}
public static class CtorHasRuntimeException {
public CtorHasRuntimeException() {
throw new RuntimeException("my message");
}
}
public static class CtorHasError {
public CtorHasError() {
throw new Error("my message again");
}
}
public void testParentConstructorThrowsDeclaredException() throws Exception {
try {
proxyFor(CtorHasDeclaredException.class).build();
fail();
} catch (UndeclaredThrowableException expected) {
assertTrue(expected.getCause() instanceof IOException);
}
try {
proxyFor(CtorHasRuntimeException.class).build();
fail();
} catch (RuntimeException expected) {
assertEquals("my message", expected.getMessage());
}
try {
proxyFor(CtorHasError.class).build();
fail();
} catch (Error expected) {
assertEquals("my message again", expected.getMessage());
}
}
public void testGetInvocationHandler_NormalOperation() throws Exception {
Object proxy = proxyFor(Object.class).build();
assertSame(fakeHandler, ProxyBuilder.getInvocationHandler(proxy));
}
public void testGetInvocationHandler_NotAProxy() {
try {
ProxyBuilder.getInvocationHandler(new Object());
fail();
} catch (IllegalArgumentException expected) {}
}
public static class ReturnsObject {
public Object getValue() {
return new Object();
}
}
public static class ReturnsString extends ReturnsObject {
@Override
public String getValue() {
return "a string";
}
}
public void testCovariantReturnTypes_NormalBehaviour() throws Exception {
String expected = "some string";
fakeHandler.setFakeResult(expected);
assertSame(expected, proxyFor(ReturnsObject.class).build().getValue());
assertSame(expected, proxyFor(ReturnsString.class).build().getValue());
}
public void testCovariantReturnTypes_WrongReturnType() throws Exception {
try {
fakeHandler.setFakeResult(new Object());
proxyFor(ReturnsString.class).build().getValue();
fail();
} catch (ClassCastException expected) {}
}
public void testCaching() throws Exception {
SimpleClass a = proxyFor(SimpleClass.class).build();
SimpleClass b = proxyFor(SimpleClass.class).build();
assertSame(a.getClass(), b.getClass());
}
public void testCachingWithMultipleConstructors() throws Exception {
HasMultipleConstructors a = ProxyBuilder.forClass(HasMultipleConstructors.class)
.constructorArgTypes()
.constructorArgValues()
.handler(fakeHandler)
.dexCache(DexMakerTest.getDataDirectory()).build();
assertEquals("no args", a.calledConstructor);
HasMultipleConstructors b = ProxyBuilder.forClass(HasMultipleConstructors.class)
.constructorArgTypes(int.class)
.constructorArgValues(2)
.handler(fakeHandler)
.dexCache(DexMakerTest.getDataDirectory()).build();
assertEquals("int 2", b.calledConstructor);
assertEquals(a.getClass(), b.getClass());
HasMultipleConstructors c = ProxyBuilder.forClass(HasMultipleConstructors.class)
.constructorArgTypes(Integer.class)
.constructorArgValues(3)
.handler(fakeHandler)
.dexCache(DexMakerTest.getDataDirectory()).build();
assertEquals("Integer 3", c.calledConstructor);
assertEquals(a.getClass(), c.getClass());
}
public static class HasMultipleConstructors {
private final String calledConstructor;
public HasMultipleConstructors() {
calledConstructor = "no args";
}
public HasMultipleConstructors(int b) {
calledConstructor = "int " + b;
}
public HasMultipleConstructors(Integer c) {
calledConstructor = "Integer " + c;
}
}
public void testClassNotCachedWithDifferentParentClassLoaders() throws Exception {
ClassLoader classLoaderA = newPathClassLoader();
SimpleClass a = proxyFor(SimpleClass.class)
.parentClassLoader(classLoaderA)
.build();
assertEquals(classLoaderA, a.getClass().getClassLoader().getParent());
ClassLoader classLoaderB = newPathClassLoader();
SimpleClass b = proxyFor(SimpleClass.class)
.parentClassLoader(classLoaderB)
.build();
assertEquals(classLoaderB, b.getClass().getClassLoader().getParent());
assertTrue(a.getClass() != b.getClass());
}
public void testAbstractClassWithUndeclaredInterfaceMethod() throws Throwable {
DeclaresInterface declaresInterface = proxyFor(DeclaresInterface.class)
.build();
assertEquals("fake result", declaresInterface.call());
try {
ProxyBuilder.callSuper(declaresInterface, Callable.class.getMethod("call"));
fail();
} catch (AbstractMethodError expected) {
}
}
public static abstract class DeclaresInterface implements Callable<String> {
}
public void testImplementingInterfaces() throws Throwable {
SimpleClass simpleClass = proxyFor(SimpleClass.class)
.implementing(Callable.class)
.implementing(Comparable.class)
.build();
assertEquals("fake result", simpleClass.simpleMethod());
Callable<?> asCallable = (Callable<?>) simpleClass;
assertEquals("fake result", asCallable.call());
Comparable<?> asComparable = (Comparable<?>) simpleClass;
fakeHandler.fakeResult = 3;
assertEquals(3, asComparable.compareTo(null));
}
public void testCallSuperWithInterfaceMethod() throws Throwable {
SimpleClass simpleClass = proxyFor(SimpleClass.class)
.implementing(Callable.class)
.build();
try {
ProxyBuilder.callSuper(simpleClass, Callable.class.getMethod("call"));
fail();
} catch (AbstractMethodError expected) {
} catch (NoSuchMethodError expected) {
}
}
public void testImplementInterfaceCallingThroughConcreteClass() throws Throwable {
InvocationHandler invocationHandler = new InvocationHandler() {
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
assertEquals("a", ProxyBuilder.callSuper(o, method, objects));
return "b";
}
};
ImplementsCallable proxy = proxyFor(ImplementsCallable.class)
.implementing(Callable.class)
.handler(invocationHandler)
.build();
assertEquals("b", proxy.call());
assertEquals("a", ProxyBuilder.callSuper(
proxy, ImplementsCallable.class.getMethod("call")));
}
/**
* This test is a bit unintuitive because it exercises the synthetic methods
* that support covariant return types. Calling 'Object call()' on the
* interface bridges to 'String call()', and so the super method appears to
* also be proxied.
*/
public void testImplementInterfaceCallingThroughInterface() throws Throwable {
final AtomicInteger count = new AtomicInteger();
InvocationHandler invocationHandler = new InvocationHandler() {
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
count.incrementAndGet();
return ProxyBuilder.callSuper(o, method, objects);
}
};
Callable<?> proxy = proxyFor(ImplementsCallable.class)
.implementing(Callable.class)
.handler(invocationHandler)
.build();
// the invocation handler is called twice!
assertEquals("a", proxy.call());
assertEquals(2, count.get());
// the invocation handler is called, even though this is a callSuper() call!
assertEquals("a", ProxyBuilder.callSuper(proxy, Callable.class.getMethod("call")));
assertEquals(3, count.get());
}
public static class ImplementsCallable implements Callable<String> {
public String call() throws Exception {
return "a";
}
}
/**
* This test shows that our generated proxies follow the bytecode convention
* where methods can have the same name but unrelated return types. This is
* different from javac's convention where return types must be assignable
* in one direction or the other.
*/
public void testInterfacesSameNamesDifferentReturnTypes() throws Throwable {
InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
if (method.getReturnType() == void.class) {
return null;
} else if (method.getReturnType() == String.class) {
return "X";
} else if (method.getReturnType() == int.class) {
return 3;
} else {
throw new AssertionFailedError();
}
}
};
Object o = proxyFor(Object.class)
.implementing(FooReturnsVoid.class, FooReturnsString.class, FooReturnsInt.class)
.handler(handler)
.build();
FooReturnsVoid a = (FooReturnsVoid) o;
a.foo();
FooReturnsString b = (FooReturnsString) o;
assertEquals("X", b.foo());
FooReturnsInt c = (FooReturnsInt) o;
assertEquals(3, c.foo());
}
public void testInterfacesSameNamesSameReturnType() throws Throwable {
Object o = proxyFor(Object.class)
.implementing(FooReturnsInt.class, FooReturnsInt2.class)
.build();
fakeHandler.setFakeResult(3);
FooReturnsInt a = (FooReturnsInt) o;
assertEquals(3, a.foo());
FooReturnsInt2 b = (FooReturnsInt2) o;
assertEquals(3, b.foo());
}
public interface FooReturnsVoid {
void foo();
}
public interface FooReturnsString {
String foo();
}
public interface FooReturnsInt {
int foo();
}
public interface FooReturnsInt2 {
int foo();
}
private ClassLoader newPathClassLoader() throws Exception {
return (ClassLoader) Class.forName("dalvik.system.PathClassLoader")
.getConstructor(String.class, ClassLoader.class)
.newInstance("", getClass().getClassLoader());
}
public void testSubclassOfRandom() throws Exception {
proxyFor(Random.class)
.handler(new InvokeSuperHandler())
.build();
}
public static class FinalToString {
@Override public final String toString() {
return "no proxy";
}
}
// https://code.google.com/p/dexmaker/issues/detail?id=12
public void testFinalToString() throws Throwable {
assertEquals("no proxy", proxyFor(FinalToString.class).build().toString());
}
/** Simple helper to add the most common args for this test to the proxy builder. */
private <T> ProxyBuilder<T> proxyFor(Class<T> clazz) throws Exception {
return ProxyBuilder.forClass(clazz)
.handler(fakeHandler)
.dexCache(DexMakerTest.getDataDirectory());
}
private static class FakeInvocationHandler implements InvocationHandler {
private Object fakeResult = "fake result";
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return fakeResult;
}
public void setFakeResult(Object result) {
fakeResult = result;
}
}
public static class TestOrderingClass {
public int returnsInt() {
return 0;
}
public int returnsInt(int param1, int param2) {
return 1;
}
public String returnsString() {
return "string";
}
public boolean returnsBoolean() {
return false;
}
public double returnsDouble() {
return 1.0;
}
public Object returnsObject() {
return new Object();
}
}
@SuppressWarnings("unchecked")
public void testMethodsGeneratedInDeterministicOrder() throws Exception {
// Grab the static methods array from the original class.
Method[] methods1 = getMethodsForProxyClass(TestOrderingClass.class);
assertNotNull(methods1);
// Clear ProxyBuilder's in-memory cache of classes. This will force
// it to rebuild the class and reset the static methods field.
Map<Class<?>, Class<?>> map = getGeneratedProxyClasses();
assertNotNull(map);
map.clear();
// Grab the static methods array from the rebuilt class.
Method[] methods2 = getMethodsForProxyClass(TestOrderingClass.class);
assertNotNull(methods2);
// Ensure that the two method arrays are equal.
assertTrue(Arrays.equals(methods1, methods2));
}
public void testOrderingClassWithDexMakerCaching() throws Exception {
doTestOrderClassWithDexMakerCaching();
// Force ProxyBuilder to call DexMaker.generateAndLoad()
getGeneratedProxyClasses().clear();
doTestOrderClassWithDexMakerCaching();
}
private void doTestOrderClassWithDexMakerCaching() throws Exception {
TestOrderingClass proxy = ProxyBuilder.forClass(TestOrderingClass.class)
.handler(new InvokeSuperHandler())
.dexCache(DexMakerTest.getDataDirectory())
.build();
assertEquals(0, proxy.returnsInt());
assertEquals(1, proxy.returnsInt(1, 1));
assertEquals("string", proxy.returnsString());
assertFalse(proxy.returnsBoolean());
assertEquals(1.0, proxy.returnsDouble());
assertNotNull(proxy.returnsObject());
assertEquals(2, versionedDxDir.listFiles().length);
}
// Returns static methods array from a proxy class.
private Method[] getMethodsForProxyClass(Class<?> parentClass) throws Exception {
Class<?> proxyClass = proxyFor(parentClass).buildProxyClass();
Method[] methods = null;
for (Field f : proxyClass.getDeclaredFields()) {
if (Method[].class.isAssignableFrom(f.getType())) {
f.setAccessible(true);
methods = (Method[]) f.get(null);
break;
}
}
return methods;
}
private Map<Class<?>, Class<?>> getGeneratedProxyClasses() throws Exception {
Field mapField = ProxyBuilder.class
.getDeclaredField("generatedProxyClasses");
mapField.setAccessible(true);
return (Map<Class<?>, Class<?>>) mapField.get(null);
}
public static class ConcreteClassA implements FooReturnsInt {
// from FooReturnsInt
public int foo() {
return 1;
}
// not from FooReturnsInt
public String bar() {
return "bar";
}
}
public static class ConcreteClassB implements FooReturnsInt {
// from FooReturnsInt
public int foo() {
return 0;
}
// not from FooReturnsInt
public String bar() {
return "bahhr";
}
}
public void testTwoClassesWithIdenticalMethodSignatures_DexMakerCaching() throws Exception {
ConcreteClassA proxyA = ProxyBuilder.forClass(ConcreteClassA.class)
.handler(new InvokeSuperHandler())
.dexCache(DexMakerTest.getDataDirectory())
.build();
assertEquals(1, proxyA.foo());
assertEquals("bar", proxyA.bar());
assertEquals(2, versionedDxDir.listFiles().length);
ConcreteClassB proxyB = ProxyBuilder.forClass(ConcreteClassB.class)
.handler(new InvokeSuperHandler())
.dexCache(DexMakerTest.getDataDirectory())
.build();
assertEquals(0, proxyB.foo());
assertEquals("bahhr", proxyB.bar());
assertEquals(4, versionedDxDir.listFiles().length);
}
}