• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.dexmaker.stock;
18 
19 import com.google.dexmaker.DexMakerTest;
20 import java.io.File;
21 import java.io.IOException;
22 import java.lang.reflect.InvocationHandler;
23 import java.lang.reflect.Method;
24 import java.lang.reflect.UndeclaredThrowableException;
25 import java.util.Arrays;
26 import java.util.Random;
27 import java.util.concurrent.Callable;
28 import java.util.concurrent.atomic.AtomicInteger;
29 import junit.framework.AssertionFailedError;
30 import junit.framework.TestCase;
31 
32 public class ProxyBuilderTest extends TestCase {
33     private FakeInvocationHandler fakeHandler = new FakeInvocationHandler();
34 
35     public static class SimpleClass {
simpleMethod()36         public String simpleMethod() {
37             throw new AssertionFailedError();
38         }
39     }
40 
testExampleOperation()41     public void testExampleOperation() throws Throwable {
42         fakeHandler.setFakeResult("expected");
43         SimpleClass proxy = proxyFor(SimpleClass.class).build();
44         assertEquals("expected", proxy.simpleMethod());
45     }
46 
47     public static class ConstructorTakesArguments {
48         private final String argument;
49 
ConstructorTakesArguments(String arg)50         public ConstructorTakesArguments(String arg) {
51             argument = arg;
52         }
53 
method()54         public String method() {
55             throw new AssertionFailedError();
56         }
57     }
58 
testConstruction_SucceedsIfCorrectArgumentsProvided()59     public void testConstruction_SucceedsIfCorrectArgumentsProvided() throws Throwable {
60         ConstructorTakesArguments proxy = proxyFor(ConstructorTakesArguments.class)
61                 .constructorArgTypes(String.class)
62                 .constructorArgValues("hello")
63                 .build();
64         assertEquals("hello", proxy.argument);
65         proxy.method();
66     }
67 
testConstruction_FailsWithWrongNumberOfArguments()68     public void testConstruction_FailsWithWrongNumberOfArguments() throws Throwable {
69         try {
70             proxyFor(ConstructorTakesArguments.class).build();
71             fail();
72         } catch (IllegalArgumentException expected) {}
73     }
74 
testClassIsNotAccessbile_FailsWithUnsupportedOperationException()75     public void testClassIsNotAccessbile_FailsWithUnsupportedOperationException() throws Exception {
76         class MethodVisibilityClass {
77         }
78         try {
79             proxyFor(MethodVisibilityClass.class).build();
80             fail();
81         } catch (UnsupportedOperationException expected) {}
82     }
83 
84     private static class PrivateVisibilityClass {
85     }
86 
testPrivateClass_FailsWithUnsupportedOperationException()87     public void testPrivateClass_FailsWithUnsupportedOperationException() throws Exception {
88         try {
89             proxyFor(PrivateVisibilityClass.class).build();
90             fail();
91         } catch (UnsupportedOperationException expected) {}
92     }
93 
94     protected static class ProtectedVisibilityClass {
foo()95         public String foo() {
96             throw new AssertionFailedError();
97         }
98     }
99 
testProtectedVisibility_WorksFine()100     public void testProtectedVisibility_WorksFine() throws Exception {
101         assertEquals("fake result", proxyFor(ProtectedVisibilityClass.class).build().foo());
102     }
103 
104     public static class HasFinalMethod {
nonFinalMethod()105         public String nonFinalMethod() {
106             return "non-final method";
107         }
108 
finalMethod()109         public final String finalMethod() {
110             return "final method";
111         }
112     }
113 
testCanProxyClassesWithFinalMethods_WillNotCallTheFinalMethod()114     public void testCanProxyClassesWithFinalMethods_WillNotCallTheFinalMethod() throws Throwable {
115         HasFinalMethod proxy = proxyFor(HasFinalMethod.class).build();
116         assertEquals("final method", proxy.finalMethod());
117         assertEquals("fake result", proxy.nonFinalMethod());
118     }
119 
120     public static class HasPrivateMethod {
result()121         private String result() {
122             return "expected";
123         }
124     }
125 
testProxyingPrivateMethods_NotIntercepted()126     public void testProxyingPrivateMethods_NotIntercepted() throws Throwable {
127         assertEquals("expected", proxyFor(HasPrivateMethod.class).build().result());
128     }
129 
130     public static class HasPackagePrivateMethod {
result()131         String result() {
132             throw new AssertionFailedError();
133         }
134     }
135 
testProxyingPackagePrivateMethods_AreIntercepted()136     public void testProxyingPackagePrivateMethods_AreIntercepted() throws Throwable {
137         assertEquals("fake result", proxyFor(HasPackagePrivateMethod.class).build().result());
138     }
139 
140     public static class HasProtectedMethod {
result()141         protected String result() {
142             throw new AssertionFailedError();
143         }
144     }
145 
testProxyingProtectedMethods_AreIntercepted()146     public void testProxyingProtectedMethods_AreIntercepted() throws Throwable {
147         assertEquals("fake result", proxyFor(HasProtectedMethod.class).build().result());
148     }
149 
150     public static class HasVoidMethod {
dangerousMethod()151         public void dangerousMethod() {
152             fail();
153         }
154     }
155 
testVoidMethod_ShouldNotThrowRuntimeException()156     public void testVoidMethod_ShouldNotThrowRuntimeException() throws Throwable {
157         proxyFor(HasVoidMethod.class).build().dangerousMethod();
158     }
159 
testObjectMethodsAreAlsoProxied()160     public void testObjectMethodsAreAlsoProxied() throws Throwable {
161         Object proxy = proxyFor(Object.class).build();
162         fakeHandler.setFakeResult("mystring");
163         assertEquals("mystring", proxy.toString());
164         fakeHandler.setFakeResult(-1);
165         assertEquals(-1, proxy.hashCode());
166         fakeHandler.setFakeResult(false);
167         assertEquals(false, proxy.equals(proxy));
168     }
169 
170     public static class AllReturnTypes {
getBoolean()171         public boolean getBoolean() { return true; }
getInt()172         public int getInt() { return 1; }
getByte()173         public byte getByte() { return 2; }
getLong()174         public long getLong() { return 3L; }
getShort()175         public short getShort() { return 4; }
getFloat()176         public float getFloat() { return 5f; }
getDouble()177         public double getDouble() { return 6.0; }
getChar()178         public char getChar() { return 'c'; }
getIntArray()179         public int[] getIntArray() { return new int[] { 8, 9 }; }
getStringArray()180         public String[] getStringArray() { return new String[] { "d", "e" }; }
181     }
182 
testAllReturnTypes()183     public void testAllReturnTypes() throws Throwable {
184         AllReturnTypes proxy = proxyFor(AllReturnTypes.class).build();
185         fakeHandler.setFakeResult(false);
186         assertEquals(false, proxy.getBoolean());
187         fakeHandler.setFakeResult(8);
188         assertEquals(8, proxy.getInt());
189         fakeHandler.setFakeResult((byte) 9);
190         assertEquals(9, proxy.getByte());
191         fakeHandler.setFakeResult(10L);
192         assertEquals(10, proxy.getLong());
193         fakeHandler.setFakeResult((short) 11);
194         assertEquals(11, proxy.getShort());
195         fakeHandler.setFakeResult(12f);
196         assertEquals(12f, proxy.getFloat());
197         fakeHandler.setFakeResult(13.0);
198         assertEquals(13.0, proxy.getDouble());
199         fakeHandler.setFakeResult('z');
200         assertEquals('z', proxy.getChar());
201         fakeHandler.setFakeResult(new int[] { -1, -2 });
202         assertEquals("[-1, -2]", Arrays.toString(proxy.getIntArray()));
203         fakeHandler.setFakeResult(new String[] { "x", "y" });
204         assertEquals("[x, y]", Arrays.toString(proxy.getStringArray()));
205     }
206 
207     public static class PassThroughAllTypes {
getBoolean(boolean input)208         public boolean getBoolean(boolean input) { return input; }
getInt(int input)209         public int getInt(int input) { return input; }
getByte(byte input)210         public byte getByte(byte input) { return input; }
getLong(long input)211         public long getLong(long input) { return input; }
getShort(short input)212         public short getShort(short input) { return input; }
getFloat(float input)213         public float getFloat(float input) { return input; }
getDouble(double input)214         public double getDouble(double input) { return input; }
getChar(char input)215         public char getChar(char input) { return input; }
getString(String input)216         public String getString(String input) { return input; }
getObject(Object input)217         public Object getObject(Object input) { return input; }
getNothing()218         public void getNothing() {}
219     }
220 
221     public static class InvokeSuperHandler implements InvocationHandler {
invoke(Object proxy, Method method, Object[] args)222         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
223             return ProxyBuilder.callSuper(proxy, method, args);
224         }
225     }
226 
testPassThroughWorksForAllTypes()227     public void testPassThroughWorksForAllTypes() throws Exception {
228         PassThroughAllTypes proxy = proxyFor(PassThroughAllTypes.class)
229                 .handler(new InvokeSuperHandler())
230                 .build();
231         assertEquals(false, proxy.getBoolean(false));
232         assertEquals(true, proxy.getBoolean(true));
233         assertEquals(0, proxy.getInt(0));
234         assertEquals(1, proxy.getInt(1));
235         assertEquals((byte) 2, proxy.getByte((byte) 2));
236         assertEquals((byte) 3, proxy.getByte((byte) 3));
237         assertEquals(4L, proxy.getLong(4L));
238         assertEquals(5L, proxy.getLong(5L));
239         assertEquals((short) 6, proxy.getShort((short) 6));
240         assertEquals((short) 7, proxy.getShort((short) 7));
241         assertEquals(8f, proxy.getFloat(8f));
242         assertEquals(9f, proxy.getFloat(9f));
243         assertEquals(10.0, proxy.getDouble(10.0));
244         assertEquals(11.0, proxy.getDouble(11.0));
245         assertEquals('a', proxy.getChar('a'));
246         assertEquals('b', proxy.getChar('b'));
247         assertEquals("asdf", proxy.getString("asdf"));
248         assertEquals("qwer", proxy.getString("qwer"));
249         assertEquals(null, proxy.getString(null));
250         Object a = new Object();
251         assertEquals(a, proxy.getObject(a));
252         assertEquals(null, proxy.getObject(null));
253         proxy.getNothing();
254     }
255 
256     public static class ExtendsAllReturnTypes extends AllReturnTypes {
example()257         public int example() { return 0; }
258     }
259 
testProxyWorksForSuperclassMethodsAlso()260     public void testProxyWorksForSuperclassMethodsAlso() throws Throwable {
261         ExtendsAllReturnTypes proxy = proxyFor(ExtendsAllReturnTypes.class).build();
262         fakeHandler.setFakeResult(99);
263         assertEquals(99, proxy.example());
264         assertEquals(99, proxy.getInt());
265         assertEquals(99, proxy.hashCode());
266     }
267 
268     public static class HasOddParams {
method(int first, Integer second)269         public long method(int first, Integer second) {
270             throw new AssertionFailedError();
271         }
272     }
273 
testMixingBoxedAndUnboxedParams()274     public void testMixingBoxedAndUnboxedParams() throws Throwable {
275         HasOddParams proxy = proxyFor(HasOddParams.class).build();
276         fakeHandler.setFakeResult(99L);
277         assertEquals(99L, proxy.method(1, Integer.valueOf(2)));
278     }
279 
280     public static class SingleInt {
getString(int value)281         public String getString(int value) {
282             throw new AssertionFailedError();
283         }
284     }
285 
testSinglePrimitiveParameter()286     public void testSinglePrimitiveParameter() throws Throwable {
287         InvocationHandler handler = new InvocationHandler() {
288             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
289                 return "asdf" + ((Integer) args[0]).intValue();
290             }
291         };
292         assertEquals("asdf1", proxyFor(SingleInt.class).handler(handler).build().getString(1));
293     }
294 
295     public static class TwoConstructors {
296         private final String string;
297 
TwoConstructors()298         public TwoConstructors() {
299             string = "no-arg";
300         }
301 
TwoConstructors(boolean unused)302         public TwoConstructors(boolean unused) {
303             string = "one-arg";
304         }
305     }
306 
testNoConstructorArguments_CallsNoArgConstructor()307     public void testNoConstructorArguments_CallsNoArgConstructor() throws Throwable {
308         TwoConstructors twoConstructors = proxyFor(TwoConstructors.class).build();
309         assertEquals("no-arg", twoConstructors.string);
310     }
311 
testWithoutInvocationHandler_ThrowsIllegalArgumentException()312     public void testWithoutInvocationHandler_ThrowsIllegalArgumentException() throws Throwable {
313         try {
314             ProxyBuilder.forClass(TwoConstructors.class)
315                     .dexCache(DexMakerTest.getDataDirectory())
316                     .build();
317             fail();
318         } catch (IllegalArgumentException expected) {}
319     }
320 
321     public static class HardToConstructCorrectly {
HardToConstructCorrectly()322         public HardToConstructCorrectly() { fail(); }
HardToConstructCorrectly(Runnable ignored)323         public HardToConstructCorrectly(Runnable ignored) { fail(); }
HardToConstructCorrectly(Exception ignored)324         public HardToConstructCorrectly(Exception ignored) { fail(); }
HardToConstructCorrectly(Boolean ignored)325         public HardToConstructCorrectly(Boolean ignored) { /* safe */ }
HardToConstructCorrectly(Integer ignored)326         public HardToConstructCorrectly(Integer ignored) { fail(); }
327     }
328 
testHardToConstruct_WorksIfYouSpecifyTheConstructorCorrectly()329     public void testHardToConstruct_WorksIfYouSpecifyTheConstructorCorrectly() throws Throwable {
330         proxyFor(HardToConstructCorrectly.class)
331                 .constructorArgTypes(Boolean.class)
332                 .constructorArgValues(true)
333                 .build();
334     }
335 
testHardToConstruct_EvenWorksWhenArgsAreAmbiguous()336     public void testHardToConstruct_EvenWorksWhenArgsAreAmbiguous() throws Throwable {
337         proxyFor(HardToConstructCorrectly.class)
338                 .constructorArgTypes(Boolean.class)
339                 .constructorArgValues(new Object[] { null })
340                 .build();
341     }
342 
testHardToConstruct_DoesNotInferTypesFromValues()343     public void testHardToConstruct_DoesNotInferTypesFromValues() throws Throwable {
344         try {
345             proxyFor(HardToConstructCorrectly.class)
346                     .constructorArgValues(true)
347                     .build();
348             fail();
349         } catch (IllegalArgumentException expected) {}
350     }
351 
testDefaultProxyHasSuperMethodToAccessOriginal()352     public void testDefaultProxyHasSuperMethodToAccessOriginal() throws Exception {
353         Object objectProxy = proxyFor(Object.class).build();
354         assertNotNull(objectProxy.getClass().getMethod("super$hashCode$int"));
355     }
356 
357     public static class PrintsOddAndValue {
method(int value)358         public String method(int value) {
359             return "odd " + value;
360         }
361     }
362 
testSometimesDelegateToSuper()363     public void testSometimesDelegateToSuper() throws Exception {
364         InvocationHandler delegatesOddValues = new InvocationHandler() {
365             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
366                 if (method.getName().equals("method")) {
367                     int intValue = ((Integer) args[0]).intValue();
368                     if (intValue % 2 == 0) {
369                         return "even " + intValue;
370                     }
371                 }
372                 return ProxyBuilder.callSuper(proxy, method, args);
373             }
374         };
375         PrintsOddAndValue proxy = proxyFor(PrintsOddAndValue.class)
376                 .handler(delegatesOddValues)
377                 .build();
378         assertEquals("even 0", proxy.method(0));
379         assertEquals("odd 1", proxy.method(1));
380         assertEquals("even 2", proxy.method(2));
381         assertEquals("odd 3", proxy.method(3));
382     }
383 
testCallSuperThrows()384     public void testCallSuperThrows() throws Exception {
385         InvocationHandler handler = new InvocationHandler() {
386             public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
387                 return ProxyBuilder.callSuper(o, method, objects);
388             }
389         };
390 
391         FooThrows fooThrows = proxyFor(FooThrows.class)
392                 .handler(handler)
393                 .build();
394 
395         try {
396             fooThrows.foo();
397             fail();
398         } catch (IllegalStateException expected) {
399             assertEquals("boom!", expected.getMessage());
400         }
401     }
402 
403     public static class FooThrows {
foo()404         public void foo() {
405             throw new IllegalStateException("boom!");
406         }
407     }
408 
409     public static class DoubleReturn {
getValue()410         public double getValue() {
411             return 2.0;
412         }
413     }
414 
testUnboxedResult()415     public void testUnboxedResult() throws Exception {
416         fakeHandler.fakeResult = 2.0;
417         assertEquals(2.0, proxyFor(DoubleReturn.class).build().getValue());
418     }
419 
staticMethod()420     public static void staticMethod() {
421     }
422 
testDoesNotOverrideStaticMethods()423     public void testDoesNotOverrideStaticMethods() throws Exception {
424         // Method should exist on this test class itself.
425         ProxyBuilderTest.class.getDeclaredMethod("staticMethod");
426         // Method should not exist on the subclass.
427         try {
428             proxyFor(ProxyBuilderTest.class).build().getClass().getDeclaredMethod("staticMethod");
429             fail();
430         } catch (NoSuchMethodException expected) {}
431     }
432 
testIllegalCacheDirectory()433     public void testIllegalCacheDirectory() throws Exception {
434         try {
435           proxyFor(ProxyForIllegalCacheDirectory.class)
436                   .dexCache(new File("/poop/"))
437                   .build();
438           fail();
439         } catch (IOException expected) {
440         }
441     }
442 
443     public static class ProxyForIllegalCacheDirectory {
444     }
445 
testInvalidConstructorSpecification()446     public void testInvalidConstructorSpecification() throws Exception {
447         try {
448             proxyFor(Object.class)
449                     .constructorArgTypes(String.class, Boolean.class)
450                     .constructorArgValues("asdf", true)
451                     .build();
452             fail();
453         } catch (IllegalArgumentException expected) {}
454     }
455 
456     public static abstract class AbstractClass {
getValue()457         public abstract Object getValue();
458     }
459 
testAbstractClassBehaviour()460     public void testAbstractClassBehaviour() throws Exception {
461         assertEquals("fake result", proxyFor(AbstractClass.class).build().getValue());
462     }
463 
464     public static class CtorHasDeclaredException {
CtorHasDeclaredException()465         public CtorHasDeclaredException() throws IOException {
466             throw new IOException();
467         }
468     }
469 
470     public static class CtorHasRuntimeException {
CtorHasRuntimeException()471         public CtorHasRuntimeException() {
472             throw new RuntimeException("my message");
473         }
474     }
475 
476     public static class CtorHasError {
CtorHasError()477         public CtorHasError() {
478             throw new Error("my message again");
479         }
480     }
481 
testParentConstructorThrowsDeclaredException()482     public void testParentConstructorThrowsDeclaredException() throws Exception {
483         try {
484             proxyFor(CtorHasDeclaredException.class).build();
485             fail();
486         } catch (UndeclaredThrowableException expected) {
487             assertTrue(expected.getCause() instanceof IOException);
488         }
489         try {
490             proxyFor(CtorHasRuntimeException.class).build();
491             fail();
492         } catch (RuntimeException expected) {
493             assertEquals("my message", expected.getMessage());
494         }
495         try {
496             proxyFor(CtorHasError.class).build();
497             fail();
498         } catch (Error expected) {
499             assertEquals("my message again", expected.getMessage());
500         }
501     }
502 
testGetInvocationHandler_NormalOperation()503     public void testGetInvocationHandler_NormalOperation() throws Exception {
504         Object proxy = proxyFor(Object.class).build();
505         assertSame(fakeHandler, ProxyBuilder.getInvocationHandler(proxy));
506     }
507 
testGetInvocationHandler_NotAProxy()508     public void testGetInvocationHandler_NotAProxy() {
509         try {
510             ProxyBuilder.getInvocationHandler(new Object());
511             fail();
512         } catch (IllegalArgumentException expected) {}
513     }
514 
515     public static class ReturnsObject {
getValue()516         public Object getValue() {
517             return new Object();
518         }
519     }
520 
521     public static class ReturnsString extends ReturnsObject {
522         @Override
getValue()523         public String getValue() {
524             return "a string";
525         }
526     }
527 
testCovariantReturnTypes_NormalBehaviour()528     public void testCovariantReturnTypes_NormalBehaviour() throws Exception {
529         String expected = "some string";
530         fakeHandler.setFakeResult(expected);
531         assertSame(expected, proxyFor(ReturnsObject.class).build().getValue());
532         assertSame(expected, proxyFor(ReturnsString.class).build().getValue());
533     }
534 
testCovariantReturnTypes_WrongReturnType()535     public void testCovariantReturnTypes_WrongReturnType() throws Exception {
536         try {
537             fakeHandler.setFakeResult(new Object());
538             proxyFor(ReturnsString.class).build().getValue();
539             fail();
540         } catch (ClassCastException expected) {}
541     }
542 
testCaching()543     public void testCaching() throws Exception {
544         SimpleClass a = proxyFor(SimpleClass.class).build();
545         SimpleClass b = proxyFor(SimpleClass.class).build();
546         assertSame(a.getClass(), b.getClass());
547     }
548 
testCachingWithMultipleConstructors()549     public void testCachingWithMultipleConstructors() throws Exception {
550         HasMultipleConstructors a = ProxyBuilder.forClass(HasMultipleConstructors.class)
551                 .constructorArgTypes()
552                 .constructorArgValues()
553                 .handler(fakeHandler)
554                 .dexCache(DexMakerTest.getDataDirectory()).build();
555         assertEquals("no args", a.calledConstructor);
556         HasMultipleConstructors b = ProxyBuilder.forClass(HasMultipleConstructors.class)
557                 .constructorArgTypes(int.class)
558                 .constructorArgValues(2)
559                 .handler(fakeHandler)
560                 .dexCache(DexMakerTest.getDataDirectory()).build();
561         assertEquals("int 2", b.calledConstructor);
562         assertEquals(a.getClass(), b.getClass());
563 
564         HasMultipleConstructors c = ProxyBuilder.forClass(HasMultipleConstructors.class)
565                 .constructorArgTypes(Integer.class)
566                 .constructorArgValues(3)
567                 .handler(fakeHandler)
568                 .dexCache(DexMakerTest.getDataDirectory()).build();
569         assertEquals("Integer 3", c.calledConstructor);
570         assertEquals(a.getClass(), c.getClass());
571     }
572 
573     public static class HasMultipleConstructors {
574         private final String calledConstructor;
HasMultipleConstructors()575         public HasMultipleConstructors() {
576             calledConstructor = "no args";
577         }
HasMultipleConstructors(int b)578         public HasMultipleConstructors(int b) {
579             calledConstructor = "int " + b;
580         }
HasMultipleConstructors(Integer c)581         public HasMultipleConstructors(Integer c) {
582             calledConstructor = "Integer " + c;
583         }
584     }
585 
testClassNotCachedWithDifferentParentClassLoaders()586     public void testClassNotCachedWithDifferentParentClassLoaders() throws Exception {
587         ClassLoader classLoaderA = newPathClassLoader();
588         SimpleClass a = proxyFor(SimpleClass.class)
589                 .parentClassLoader(classLoaderA)
590                 .build();
591         assertEquals(classLoaderA, a.getClass().getClassLoader().getParent());
592 
593         ClassLoader classLoaderB = newPathClassLoader();
594         SimpleClass b = proxyFor(SimpleClass.class)
595                 .parentClassLoader(classLoaderB)
596                 .build();
597         assertEquals(classLoaderB, b.getClass().getClassLoader().getParent());
598 
599         assertTrue(a.getClass() != b.getClass());
600     }
601 
testAbstractClassWithUndeclaredInterfaceMethod()602     public void testAbstractClassWithUndeclaredInterfaceMethod() throws Throwable {
603         DeclaresInterface declaresInterface = proxyFor(DeclaresInterface.class)
604                 .build();
605         assertEquals("fake result", declaresInterface.call());
606         try {
607             ProxyBuilder.callSuper(declaresInterface, Callable.class.getMethod("call"));
608             fail();
609         } catch (AbstractMethodError expected) {
610         }
611     }
612 
613     public static abstract class DeclaresInterface implements Callable<String> {
614     }
615 
testImplementingInterfaces()616     public void testImplementingInterfaces() throws Throwable {
617         SimpleClass simpleClass = proxyFor(SimpleClass.class)
618                 .implementing(Callable.class)
619                 .implementing(Comparable.class)
620                 .build();
621         assertEquals("fake result", simpleClass.simpleMethod());
622 
623         Callable<?> asCallable = (Callable<?>) simpleClass;
624         assertEquals("fake result", asCallable.call());
625 
626         Comparable<?> asComparable = (Comparable<?>) simpleClass;
627         fakeHandler.fakeResult = 3;
628         assertEquals(3, asComparable.compareTo(null));
629     }
630 
testCallSuperWithInterfaceMethod()631     public void testCallSuperWithInterfaceMethod() throws Throwable {
632         SimpleClass simpleClass = proxyFor(SimpleClass.class)
633                 .implementing(Callable.class)
634                 .build();
635         try {
636             ProxyBuilder.callSuper(simpleClass, Callable.class.getMethod("call"));
637             fail();
638         } catch (AbstractMethodError expected) {
639         } catch (NoSuchMethodError expected) {
640         }
641     }
642 
testImplementInterfaceCallingThroughConcreteClass()643     public void testImplementInterfaceCallingThroughConcreteClass() throws Throwable {
644         InvocationHandler invocationHandler = new InvocationHandler() {
645             public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
646                 assertEquals("a", ProxyBuilder.callSuper(o, method, objects));
647                 return "b";
648             }
649         };
650         ImplementsCallable proxy = proxyFor(ImplementsCallable.class)
651                 .implementing(Callable.class)
652                 .handler(invocationHandler)
653                 .build();
654         assertEquals("b", proxy.call());
655         assertEquals("a", ProxyBuilder.callSuper(
656                 proxy, ImplementsCallable.class.getMethod("call")));
657     }
658 
659     /**
660      * This test is a bit unintuitive because it exercises the synthetic methods
661      * that support covariant return types. Calling 'Object call()' on the
662      * interface bridges to 'String call()', and so the super method appears to
663      * also be proxied.
664      */
testImplementInterfaceCallingThroughInterface()665     public void testImplementInterfaceCallingThroughInterface() throws Throwable {
666         final AtomicInteger count = new AtomicInteger();
667 
668         InvocationHandler invocationHandler = new InvocationHandler() {
669             public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
670                 count.incrementAndGet();
671                 return ProxyBuilder.callSuper(o, method, objects);
672             }
673         };
674 
675         Callable<?> proxy = proxyFor(ImplementsCallable.class)
676                 .implementing(Callable.class)
677                 .handler(invocationHandler)
678                 .build();
679 
680         // the invocation handler is called twice!
681         assertEquals("a", proxy.call());
682         assertEquals(2, count.get());
683 
684         // the invocation handler is called, even though this is a callSuper() call!
685         assertEquals("a", ProxyBuilder.callSuper(proxy, Callable.class.getMethod("call")));
686         assertEquals(3, count.get());
687     }
688 
689     public static class ImplementsCallable implements Callable<String> {
call()690         public String call() throws Exception {
691             return "a";
692         }
693     }
694 
695     /**
696      * This test shows that our generated proxies follow the bytecode convention
697      * where methods can have the same name but unrelated return types. This is
698      * different from javac's convention where return types must be assignable
699      * in one direction or the other.
700      */
testInterfacesSameNamesDifferentReturnTypes()701     public void testInterfacesSameNamesDifferentReturnTypes() throws Throwable {
702         InvocationHandler handler = new InvocationHandler() {
703             public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
704                 if (method.getReturnType() == void.class) {
705                     return null;
706                 } else if (method.getReturnType() == String.class) {
707                     return "X";
708                 } else if (method.getReturnType() == int.class) {
709                     return 3;
710                 } else {
711                     throw new AssertionFailedError();
712                 }
713             }
714         };
715 
716         Object o = proxyFor(Object.class)
717                 .implementing(FooReturnsVoid.class, FooReturnsString.class, FooReturnsInt.class)
718                 .handler(handler)
719                 .build();
720 
721         FooReturnsVoid a = (FooReturnsVoid) o;
722         a.foo();
723 
724         FooReturnsString b = (FooReturnsString) o;
725         assertEquals("X", b.foo());
726 
727         FooReturnsInt c = (FooReturnsInt) o;
728         assertEquals(3, c.foo());
729     }
730 
testInterfacesSameNamesSameReturnType()731     public void testInterfacesSameNamesSameReturnType() throws Throwable {
732         Object o = proxyFor(Object.class)
733                 .implementing(FooReturnsInt.class, FooReturnsInt2.class)
734                 .build();
735 
736         fakeHandler.setFakeResult(3);
737 
738         FooReturnsInt a = (FooReturnsInt) o;
739         assertEquals(3, a.foo());
740 
741         FooReturnsInt2 b = (FooReturnsInt2) o;
742         assertEquals(3, b.foo());
743     }
744 
745     public interface FooReturnsVoid {
foo()746         void foo();
747     }
748 
749     public interface FooReturnsString {
foo()750         String foo();
751     }
752 
753     public interface FooReturnsInt {
foo()754         int foo();
755     }
756 
757     public interface FooReturnsInt2 {
foo()758         int foo();
759     }
760 
newPathClassLoader()761     private ClassLoader newPathClassLoader() throws Exception {
762         return (ClassLoader) Class.forName("dalvik.system.PathClassLoader")
763                 .getConstructor(String.class, ClassLoader.class)
764                 .newInstance("", getClass().getClassLoader());
765 
766     }
767 
testSubclassOfRandom()768     public void testSubclassOfRandom() throws Exception {
769         proxyFor(Random.class)
770                 .handler(new InvokeSuperHandler())
771                 .build();
772     }
773 
774     public static class FinalToString {
toString()775         @Override public final String toString() {
776             return "no proxy";
777         }
778     }
779 
780     // https://code.google.com/p/dexmaker/issues/detail?id=12
testFinalToString()781     public void testFinalToString() throws Throwable {
782         assertEquals("no proxy", proxyFor(FinalToString.class).build().toString());
783     }
784 
785     /** Simple helper to add the most common args for this test to the proxy builder. */
proxyFor(Class<T> clazz)786     private <T> ProxyBuilder<T> proxyFor(Class<T> clazz) throws Exception {
787         return ProxyBuilder.forClass(clazz)
788                 .handler(fakeHandler)
789                 .dexCache(DexMakerTest.getDataDirectory());
790     }
791 
792     private static class FakeInvocationHandler implements InvocationHandler {
793         private Object fakeResult = "fake result";
794 
invoke(Object proxy, Method method, Object[] args)795         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
796             return fakeResult;
797         }
798 
setFakeResult(Object result)799         public void setFakeResult(Object result) {
800             fakeResult = result;
801         }
802     }
803 }
804