• 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.android.dx.stock;
18 
19 import com.android.dx.DexMakerTest;
20 import junit.framework.AssertionFailedError;
21 import org.junit.After;
22 import org.junit.Before;
23 import org.junit.Test;
24 
25 import java.io.File;
26 import java.io.IOException;
27 import java.lang.reflect.Field;
28 import java.lang.reflect.InvocationHandler;
29 import java.lang.reflect.Method;
30 import java.lang.reflect.UndeclaredThrowableException;
31 import java.util.Arrays;
32 import java.util.Map;
33 import java.util.Random;
34 import java.util.concurrent.Callable;
35 import java.util.concurrent.atomic.AtomicInteger;
36 
37 import static com.android.dx.util.TestUtil.DELTA_DOUBLE;
38 import static com.android.dx.util.TestUtil.DELTA_FLOAT;
39 import static junit.framework.Assert.assertFalse;
40 import static junit.framework.Assert.assertNotNull;
41 import static junit.framework.Assert.assertSame;
42 import static junit.framework.Assert.assertTrue;
43 import static junit.framework.Assert.fail;
44 import static org.junit.Assert.assertEquals;
45 
46 public class ProxyBuilderTest {
47     private FakeInvocationHandler fakeHandler = new FakeInvocationHandler();
48     private File versionedDxDir = new File(DexMakerTest.getDataDirectory(), "v1");
49 
50     @Before
setUp()51     public void setUp() throws Exception {
52         versionedDxDir.mkdirs();
53         clearVersionedDxDir();
54         getGeneratedProxyClasses().clear();
55     }
56 
57     @After
tearDown()58     public void tearDown() throws Exception {
59         getGeneratedProxyClasses().clear();
60         clearVersionedDxDir();
61     }
62 
clearVersionedDxDir()63     private void clearVersionedDxDir() {
64         for (File file : versionedDxDir.listFiles()) {
65             file.delete();
66         }
67     }
68 
69     public static class SimpleClass {
simpleMethod()70         public String simpleMethod() {
71             throw new AssertionFailedError();
72         }
73     }
74 
75     public static class ExampleClass {
exampleMethod()76         public String exampleMethod() {
77             throw new AssertionFailedError();
78         }
79     }
80 
81     public static class ExampleOperationClass {
exampleMethod()82         public String exampleMethod() {
83             throw new AssertionFailedError();
84         }
85     }
86 
87     @Test
testExampleOperation()88     public void testExampleOperation() throws Throwable {
89         fakeHandler.setFakeResult("expected");
90         ExampleClass proxy = proxyFor(ExampleClass.class).build();
91         assertEquals("expected", proxy.exampleMethod());
92         assertEquals(2, versionedDxDir.listFiles().length);
93     }
94 
95     @Test
testExampleOperation_DexMakerCaching()96     public void testExampleOperation_DexMakerCaching() throws Throwable {
97         fakeHandler.setFakeResult("expected");
98         ExampleOperationClass proxy = proxyFor(ExampleOperationClass.class).build();
99         assertEquals(2, versionedDxDir.listFiles().length);
100         assertEquals("expected", proxy.exampleMethod());
101 
102         // Force ProxyBuilder to create a DexMaker generator and call DexMaker.generateAndLoad().
103         getGeneratedProxyClasses().clear();
104 
105         proxy = proxyFor(ExampleOperationClass.class).build();
106         assertEquals(2, versionedDxDir.listFiles().length);
107         assertEquals("expected", proxy.exampleMethod());
108     }
109 
110     public static class ConstructorTakesArguments {
111         private final String argument;
112 
ConstructorTakesArguments(String arg)113         public ConstructorTakesArguments(String arg) {
114             argument = arg;
115         }
116 
method()117         public String method() {
118             throw new AssertionFailedError();
119         }
120     }
121 
122     @Test
testConstruction_SucceedsIfCorrectArgumentsProvided()123     public void testConstruction_SucceedsIfCorrectArgumentsProvided() throws Throwable {
124         ConstructorTakesArguments proxy = proxyFor(ConstructorTakesArguments.class)
125                 .constructorArgTypes(String.class)
126                 .constructorArgValues("hello")
127                 .build();
128         assertEquals("hello", proxy.argument);
129         proxy.method();
130     }
131 
132     @Test(expected = IllegalArgumentException.class)
testConstruction_FailsWithWrongNumberOfArguments()133     public void testConstruction_FailsWithWrongNumberOfArguments() throws Throwable {
134         proxyFor(ConstructorTakesArguments.class).build();
135     }
136 
137     @Test(expected = UnsupportedOperationException.class)
testClassIsNotAccessbile_FailsWithUnsupportedOperationException()138     public void testClassIsNotAccessbile_FailsWithUnsupportedOperationException() throws Exception {
139         class MethodVisibilityClass {}
140         proxyFor(MethodVisibilityClass.class).build();
141     }
142 
143     private static class PrivateVisibilityClass {}
144 
145     @Test(expected = UnsupportedOperationException.class)
testPrivateClass_FailsWithUnsupportedOperationException()146     public void testPrivateClass_FailsWithUnsupportedOperationException() throws Exception {
147         proxyFor(PrivateVisibilityClass.class).build();
148     }
149 
150     protected static class ProtectedVisibilityClass {
foo()151         public String foo() {
152             throw new AssertionFailedError();
153         }
154     }
155 
156     @Test
testProtectedVisibility_WorksFine()157     public void testProtectedVisibility_WorksFine() throws Exception {
158         assertEquals("fake result", proxyFor(ProtectedVisibilityClass.class).build().foo());
159     }
160 
161     public static class HasFinalMethod {
nonFinalMethod()162         public String nonFinalMethod() {
163             return "non-final method";
164         }
165 
finalMethod()166         public final String finalMethod() {
167             return "final method";
168         }
169     }
170 
171     @Test
testCanProxyClassesWithFinalMethods_WillNotCallTheFinalMethod()172     public void testCanProxyClassesWithFinalMethods_WillNotCallTheFinalMethod() throws Throwable {
173         HasFinalMethod proxy = proxyFor(HasFinalMethod.class).build();
174         assertEquals("final method", proxy.finalMethod());
175         assertEquals("fake result", proxy.nonFinalMethod());
176     }
177 
178     public static class HasPrivateMethod {
result()179         private String result() {
180             return "expected";
181         }
182     }
183 
184     @Test
testProxyingPrivateMethods_NotIntercepted()185     public void testProxyingPrivateMethods_NotIntercepted() throws Throwable {
186         HasPrivateMethod proxy = proxyFor(HasPrivateMethod.class).build();
187         try {
188             proxy.getClass().getDeclaredMethod("result");
189             fail();
190         } catch (NoSuchMethodException expected) {
191 
192         }
193 
194         assertEquals("expected", proxy.result());
195     }
196 
197     public static class HasPackagePrivateMethod {
result()198         String result() {
199             throw new AssertionFailedError();
200         }
201     }
202 
testProxyingPackagePrivateMethods_NotIntercepted()203     public void testProxyingPackagePrivateMethods_NotIntercepted()
204             throws Throwable {
205         HasPackagePrivateMethod proxy = proxyFor(HasPackagePrivateMethod.class)
206                 .build();
207         try {
208             proxy.getClass().getDeclaredMethod("result");
209             fail();
210         } catch (NoSuchMethodException expected) {
211 
212         }
213 
214         try {
215             proxy.result();
216             fail();
217         } catch (AssertionFailedError expected) {
218 
219         }
220     }
221 
222     public static class HasProtectedMethod {
result()223         protected String result() {
224             throw new AssertionFailedError();
225         }
226     }
227 
228     @Test
testProxyingProtectedMethods_AreIntercepted()229     public void testProxyingProtectedMethods_AreIntercepted() throws Throwable {
230         assertEquals("fake result", proxyFor(HasProtectedMethod.class).build().result());
231     }
232 
233     public static class MyParentClass {
someMethod()234         String someMethod() {
235             return "package";
236         }
237     }
238 
239     public static class MyChildClassWithProtectedMethod extends MyParentClass {
240         @Override
someMethod()241         protected String someMethod() {
242             return "protected";
243         }
244     }
245 
246     public static class MyChildClassWithPublicMethod extends MyParentClass {
247         @Override
someMethod()248         public String someMethod() {
249             return "public";
250         }
251     }
252 
253     @Test
testProxying_ClassHierarchy()254     public void testProxying_ClassHierarchy() throws Throwable {
255         assertEquals("package", proxyFor(MyParentClass.class).build().someMethod());
256         assertEquals("fake result", proxyFor(MyChildClassWithProtectedMethod.class).build().someMethod());
257         assertEquals("fake result", proxyFor(MyChildClassWithPublicMethod.class).build().someMethod());
258     }
259 
260     public static class HasVoidMethod {
dangerousMethod()261         public void dangerousMethod() {
262             fail();
263         }
264     }
265 
266     @Test
testVoidMethod_ShouldNotThrowRuntimeException()267     public void testVoidMethod_ShouldNotThrowRuntimeException() throws Throwable {
268         proxyFor(HasVoidMethod.class).build().dangerousMethod();
269     }
270 
271     @Test
272     @SuppressWarnings("EqualsWithItself")
testObjectMethodsAreAlsoProxied()273     public void testObjectMethodsAreAlsoProxied() throws Throwable {
274         Object proxy = proxyFor(Object.class).build();
275         fakeHandler.setFakeResult("mystring");
276         assertEquals("mystring", proxy.toString());
277         fakeHandler.setFakeResult(-1);
278         assertEquals(-1, proxy.hashCode());
279         fakeHandler.setFakeResult(false);
280         assertEquals(false, proxy.equals(proxy));
281     }
282 
283     public static class AllReturnTypes {
getBoolean()284         public boolean getBoolean() { return true; }
getInt()285         public int getInt() { return 1; }
getByte()286         public byte getByte() { return 2; }
getLong()287         public long getLong() { return 3L; }
getShort()288         public short getShort() { return 4; }
getFloat()289         public float getFloat() { return 5f; }
getDouble()290         public double getDouble() { return 6.0; }
getChar()291         public char getChar() { return 'c'; }
getIntArray()292         public int[] getIntArray() { return new int[] { 8, 9 }; }
getStringArray()293         public String[] getStringArray() { return new String[] { "d", "e" }; }
294     }
295 
296     @Test
testAllReturnTypes()297     public void testAllReturnTypes() throws Throwable {
298         AllReturnTypes proxy = proxyFor(AllReturnTypes.class).build();
299         fakeHandler.setFakeResult(false);
300         assertEquals(false, proxy.getBoolean());
301         fakeHandler.setFakeResult(8);
302         assertEquals(8, proxy.getInt());
303         fakeHandler.setFakeResult((byte) 9);
304         assertEquals(9, proxy.getByte());
305         fakeHandler.setFakeResult(10L);
306         assertEquals(10, proxy.getLong());
307         fakeHandler.setFakeResult((short) 11);
308         assertEquals(11, proxy.getShort());
309         fakeHandler.setFakeResult(12f);
310         assertEquals(12f, proxy.getFloat(), DELTA_FLOAT);
311         fakeHandler.setFakeResult(13.0);
312         assertEquals(13.0, proxy.getDouble(), DELTA_FLOAT);
313         fakeHandler.setFakeResult('z');
314         assertEquals('z', proxy.getChar());
315         fakeHandler.setFakeResult(new int[] { -1, -2 });
316         assertEquals("[-1, -2]", Arrays.toString(proxy.getIntArray()));
317         fakeHandler.setFakeResult(new String[] { "x", "y" });
318         assertEquals("[x, y]", Arrays.toString(proxy.getStringArray()));
319     }
320 
321     public static class PassThroughAllTypes {
getBoolean(boolean input)322         public boolean getBoolean(boolean input) { return input; }
getInt(int input)323         public int getInt(int input) { return input; }
getByte(byte input)324         public byte getByte(byte input) { return input; }
getLong(long input)325         public long getLong(long input) { return input; }
getShort(short input)326         public short getShort(short input) { return input; }
getFloat(float input)327         public float getFloat(float input) { return input; }
getDouble(double input)328         public double getDouble(double input) { return input; }
getChar(char input)329         public char getChar(char input) { return input; }
getString(String input)330         public String getString(String input) { return input; }
getObject(Object input)331         public Object getObject(Object input) { return input; }
getNothing()332         public void getNothing() {}
333     }
334 
335     public static class InvokeSuperHandler implements InvocationHandler {
invoke(Object proxy, Method method, Object[] args)336         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
337             return ProxyBuilder.callSuper(proxy, method, args);
338         }
339     }
340 
341     @Test
testPassThroughWorksForAllTypes()342     public void testPassThroughWorksForAllTypes() throws Exception {
343         PassThroughAllTypes proxy = proxyFor(PassThroughAllTypes.class)
344                 .handler(new InvokeSuperHandler())
345                 .build();
346         assertEquals(false, proxy.getBoolean(false));
347         assertEquals(true, proxy.getBoolean(true));
348         assertEquals(0, proxy.getInt(0));
349         assertEquals(1, proxy.getInt(1));
350         assertEquals((byte) 2, proxy.getByte((byte) 2));
351         assertEquals((byte) 3, proxy.getByte((byte) 3));
352         assertEquals(4L, proxy.getLong(4L));
353         assertEquals(5L, proxy.getLong(5L));
354         assertEquals((short) 6, proxy.getShort((short) 6));
355         assertEquals((short) 7, proxy.getShort((short) 7));
356         assertEquals(8f, proxy.getFloat(8f), DELTA_FLOAT);
357         assertEquals(9f, proxy.getFloat(9f), DELTA_FLOAT);
358         assertEquals(10.0, proxy.getDouble(10.0), DELTA_DOUBLE);
359         assertEquals(11.0, proxy.getDouble(11.0), DELTA_DOUBLE);
360         assertEquals('a', proxy.getChar('a'));
361         assertEquals('b', proxy.getChar('b'));
362         assertEquals("asdf", proxy.getString("asdf"));
363         assertEquals("qwer", proxy.getString("qwer"));
364         assertEquals(null, proxy.getString(null));
365         Object a = new Object();
366         assertEquals(a, proxy.getObject(a));
367         assertEquals(null, proxy.getObject(null));
368         proxy.getNothing();
369     }
370 
371     public static class ExtendsAllReturnTypes extends AllReturnTypes {
example()372         public int example() { return 0; }
373     }
374 
375     @Test
testProxyWorksForSuperclassMethodsAlso()376     public void testProxyWorksForSuperclassMethodsAlso() throws Throwable {
377         ExtendsAllReturnTypes proxy = proxyFor(ExtendsAllReturnTypes.class).build();
378         fakeHandler.setFakeResult(99);
379         assertEquals(99, proxy.example());
380         assertEquals(99, proxy.getInt());
381         assertEquals(99, proxy.hashCode());
382     }
383 
384     public static class HasOddParams {
method(int first, Integer second)385         public long method(int first, Integer second) {
386             throw new AssertionFailedError();
387         }
388     }
389 
390     @Test
testMixingBoxedAndUnboxedParams()391     public void testMixingBoxedAndUnboxedParams() throws Throwable {
392         HasOddParams proxy = proxyFor(HasOddParams.class).build();
393         fakeHandler.setFakeResult(99L);
394         assertEquals(99L, proxy.method(1, Integer.valueOf(2)));
395     }
396 
397     public static class SingleInt {
getString(int value)398         public String getString(int value) {
399             throw new AssertionFailedError();
400         }
401     }
402 
403     @Test
testSinglePrimitiveParameter()404     public void testSinglePrimitiveParameter() throws Throwable {
405         InvocationHandler handler = new InvocationHandler() {
406             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
407                 return "asdf" + ((Integer) args[0]).intValue();
408             }
409         };
410         assertEquals("asdf1", proxyFor(SingleInt.class).handler(handler).build().getString(1));
411     }
412 
413     public static class TwoConstructors {
414         private final String string;
415 
TwoConstructors()416         public TwoConstructors() {
417             string = "no-arg";
418         }
419 
TwoConstructors(boolean unused)420         public TwoConstructors(boolean unused) {
421             string = "one-arg";
422         }
423     }
424 
425     @Test
testNoConstructorArguments_CallsNoArgConstructor()426     public void testNoConstructorArguments_CallsNoArgConstructor() throws Throwable {
427         TwoConstructors twoConstructors = proxyFor(TwoConstructors.class).build();
428         assertEquals("no-arg", twoConstructors.string);
429     }
430 
431     @Test
testWithoutInvocationHandler_ThrowsIllegalArgumentException()432     public void testWithoutInvocationHandler_ThrowsIllegalArgumentException() throws Throwable {
433         try {
434             ProxyBuilder.forClass(TwoConstructors.class)
435                     .dexCache(DexMakerTest.getDataDirectory())
436                     .build();
437             fail();
438         } catch (IllegalArgumentException expected) {}
439     }
440 
441     public static class HardToConstructCorrectly {
HardToConstructCorrectly()442         public HardToConstructCorrectly() { fail(); }
HardToConstructCorrectly(Runnable ignored)443         public HardToConstructCorrectly(Runnable ignored) { fail(); }
HardToConstructCorrectly(Exception ignored)444         public HardToConstructCorrectly(Exception ignored) { fail(); }
HardToConstructCorrectly(Boolean ignored)445         public HardToConstructCorrectly(Boolean ignored) { /* safe */ }
HardToConstructCorrectly(Integer ignored)446         public HardToConstructCorrectly(Integer ignored) { fail(); }
447     }
448 
449     @Test
testHardToConstruct_WorksIfYouSpecifyTheConstructorCorrectly()450     public void testHardToConstruct_WorksIfYouSpecifyTheConstructorCorrectly() throws Throwable {
451         proxyFor(HardToConstructCorrectly.class)
452                 .constructorArgTypes(Boolean.class)
453                 .constructorArgValues(true)
454                 .build();
455     }
456 
457     @Test
testHardToConstruct_EvenWorksWhenArgsAreAmbiguous()458     public void testHardToConstruct_EvenWorksWhenArgsAreAmbiguous() throws Throwable {
459         proxyFor(HardToConstructCorrectly.class)
460                 .constructorArgTypes(Boolean.class)
461                 .constructorArgValues(new Object[] { null })
462                 .build();
463     }
464 
465     @Test(expected = IllegalArgumentException.class)
testHardToConstruct_DoesNotInferTypesFromValues()466     public void testHardToConstruct_DoesNotInferTypesFromValues() throws Throwable {
467         proxyFor(HardToConstructCorrectly.class)
468                 .constructorArgValues(true)
469                 .build();
470     }
471 
472     @Test
testDefaultProxyHasSuperMethodToAccessOriginal()473     public void testDefaultProxyHasSuperMethodToAccessOriginal() throws Exception {
474         Object objectProxy = proxyFor(Object.class).build();
475         assertNotNull(objectProxy.getClass().getMethod("super$hashCode$int"));
476     }
477 
478     public static class PrintsOddAndValue {
method(int value)479         public String method(int value) {
480             return "odd " + value;
481         }
482     }
483 
484     @Test
testSometimesDelegateToSuper()485     public void testSometimesDelegateToSuper() throws Exception {
486         InvocationHandler delegatesOddValues = new InvocationHandler() {
487             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
488                 if (method.getName().equals("method")) {
489                     int intValue = ((Integer) args[0]).intValue();
490                     if (intValue % 2 == 0) {
491                         return "even " + intValue;
492                     }
493                 }
494                 return ProxyBuilder.callSuper(proxy, method, args);
495             }
496         };
497         PrintsOddAndValue proxy = proxyFor(PrintsOddAndValue.class)
498                 .handler(delegatesOddValues)
499                 .build();
500         assertEquals("even 0", proxy.method(0));
501         assertEquals("odd 1", proxy.method(1));
502         assertEquals("even 2", proxy.method(2));
503         assertEquals("odd 3", proxy.method(3));
504     }
505 
506     @Test
testCallSuperThrows()507     public void testCallSuperThrows() throws Exception {
508         InvocationHandler handler = new InvocationHandler() {
509             public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
510                 return ProxyBuilder.callSuper(o, method, objects);
511             }
512         };
513 
514         FooThrows fooThrows = proxyFor(FooThrows.class)
515                 .handler(handler)
516                 .build();
517 
518         try {
519             fooThrows.foo();
520             fail();
521         } catch (IllegalStateException expected) {
522             assertEquals("boom!", expected.getMessage());
523         }
524     }
525 
526     public static class FooThrows {
foo()527         public void foo() {
528             throw new IllegalStateException("boom!");
529         }
530     }
531 
532     public static class DoubleReturn {
getValue()533         public double getValue() {
534             return 2.0;
535         }
536     }
537 
538     @Test
testUnboxedResult()539     public void testUnboxedResult() throws Exception {
540         fakeHandler.fakeResult = 2.0;
541         assertEquals(2.0, proxyFor(DoubleReturn.class).build().getValue(), DELTA_DOUBLE);
542     }
543 
staticMethod()544     public static void staticMethod() {}
545 
546     @Test
testDoesNotOverrideStaticMethods()547     public void testDoesNotOverrideStaticMethods() throws Exception {
548         // Method should exist on this test class itself.
549         ProxyBuilderTest.class.getDeclaredMethod("staticMethod");
550         // Method should not exist on the subclass.
551         try {
552             proxyFor(ProxyBuilderTest.class).build().getClass().getDeclaredMethod("staticMethod");
553             fail();
554         } catch (NoSuchMethodException expected) {}
555     }
556 
557     @Test(expected = IOException.class)
testIllegalCacheDirectory()558     public void testIllegalCacheDirectory() throws Exception {
559         proxyFor(ProxyForIllegalCacheDirectory.class)
560                 .dexCache(new File("/poop/"))
561                 .build();
562     }
563 
564     public static class ProxyForIllegalCacheDirectory {}
565 
566     @Test(expected = IllegalArgumentException.class)
testInvalidConstructorSpecification()567     public void testInvalidConstructorSpecification() throws Exception {
568         proxyFor(Object.class)
569                 .constructorArgTypes(String.class, Boolean.class)
570                 .constructorArgValues("asdf", true)
571                 .build();
572     }
573 
574     public static abstract class AbstractClass {
getValue()575         public abstract Object getValue();
576     }
577 
578     @Test
testAbstractClassBehaviour()579     public void testAbstractClassBehaviour() throws Exception {
580         assertEquals("fake result", proxyFor(AbstractClass.class).build().getValue());
581     }
582 
583     public static class CtorHasDeclaredException {
CtorHasDeclaredException()584         public CtorHasDeclaredException() throws IOException {
585             throw new IOException();
586         }
587     }
588 
589     public static class CtorHasRuntimeException {
CtorHasRuntimeException()590         public CtorHasRuntimeException() {
591             throw new RuntimeException("my message");
592         }
593     }
594 
595     public static class CtorHasError {
CtorHasError()596         public CtorHasError() {
597             throw new Error("my message again");
598         }
599     }
600 
601     @Test
testParentConstructorThrowsDeclaredException()602     public void testParentConstructorThrowsDeclaredException() throws Exception {
603         try {
604             proxyFor(CtorHasDeclaredException.class).build();
605             fail();
606         } catch (UndeclaredThrowableException expected) {
607             assertTrue(expected.getCause() instanceof IOException);
608         }
609         try {
610             proxyFor(CtorHasRuntimeException.class).build();
611             fail();
612         } catch (RuntimeException expected) {
613             assertEquals("my message", expected.getMessage());
614         }
615         try {
616             proxyFor(CtorHasError.class).build();
617             fail();
618         } catch (Error expected) {
619             assertEquals("my message again", expected.getMessage());
620         }
621     }
622 
623     @Test
testGetInvocationHandler_NormalOperation()624     public void testGetInvocationHandler_NormalOperation() throws Exception {
625         Object proxy = proxyFor(Object.class).build();
626         assertSame(fakeHandler, ProxyBuilder.getInvocationHandler(proxy));
627     }
628 
629     @Test(expected = IllegalArgumentException.class)
testGetInvocationHandler_NotAProxy()630     public void testGetInvocationHandler_NotAProxy() {
631         ProxyBuilder.getInvocationHandler(new Object());
632     }
633 
634     public static class ReturnsObject {
getValue()635         public Object getValue() {
636             return new Object();
637         }
638     }
639 
640     public static class ReturnsString extends ReturnsObject {
641         @Override
getValue()642         public String getValue() {
643             return "a string";
644         }
645     }
646 
647     @Test
testCovariantReturnTypes_NormalBehaviour()648     public void testCovariantReturnTypes_NormalBehaviour() throws Exception {
649         String expected = "some string";
650         fakeHandler.setFakeResult(expected);
651         assertSame(expected, proxyFor(ReturnsObject.class).build().getValue());
652         assertSame(expected, proxyFor(ReturnsString.class).build().getValue());
653     }
654 
655     @Test
testCovariantReturnTypes_WrongReturnType()656     public void testCovariantReturnTypes_WrongReturnType() throws Exception {
657         try {
658             fakeHandler.setFakeResult(new Object());
659             proxyFor(ReturnsString.class).build().getValue();
660             fail();
661         } catch (ClassCastException expected) {}
662     }
663 
664     @Test
testCaching()665     public void testCaching() throws Exception {
666         SimpleClass a = proxyFor(SimpleClass.class).build();
667         SimpleClass b = proxyFor(SimpleClass.class).build();
668         assertSame(a.getClass(), b.getClass());
669     }
670 
671     @Test
testCachingWithMultipleConstructors()672     public void testCachingWithMultipleConstructors() throws Exception {
673         HasMultipleConstructors a = ProxyBuilder.forClass(HasMultipleConstructors.class)
674                 .constructorArgTypes()
675                 .constructorArgValues()
676                 .handler(fakeHandler)
677                 .dexCache(DexMakerTest.getDataDirectory()).build();
678         assertEquals("no args", a.calledConstructor);
679         HasMultipleConstructors b = ProxyBuilder.forClass(HasMultipleConstructors.class)
680                 .constructorArgTypes(int.class)
681                 .constructorArgValues(2)
682                 .handler(fakeHandler)
683                 .dexCache(DexMakerTest.getDataDirectory()).build();
684         assertEquals("int 2", b.calledConstructor);
685         assertEquals(a.getClass(), b.getClass());
686 
687         HasMultipleConstructors c = ProxyBuilder.forClass(HasMultipleConstructors.class)
688                 .constructorArgTypes(Integer.class)
689                 .constructorArgValues(3)
690                 .handler(fakeHandler)
691                 .dexCache(DexMakerTest.getDataDirectory()).build();
692         assertEquals("Integer 3", c.calledConstructor);
693         assertEquals(a.getClass(), c.getClass());
694     }
695 
696     public static class HasMultipleConstructors {
697         private final String calledConstructor;
HasMultipleConstructors()698         public HasMultipleConstructors() {
699             calledConstructor = "no args";
700         }
HasMultipleConstructors(int b)701         public HasMultipleConstructors(int b) {
702             calledConstructor = "int " + b;
703         }
HasMultipleConstructors(Integer c)704         public HasMultipleConstructors(Integer c) {
705             calledConstructor = "Integer " + c;
706         }
707     }
708 
709     @Test
testClassNotCachedWithDifferentParentClassLoaders()710     public void testClassNotCachedWithDifferentParentClassLoaders() throws Exception {
711         ClassLoader classLoaderA = newPathClassLoader();
712         SimpleClass a = proxyFor(SimpleClass.class)
713                 .parentClassLoader(classLoaderA)
714                 .build();
715         assertEquals(classLoaderA, a.getClass().getClassLoader().getParent());
716 
717         ClassLoader classLoaderB = newPathClassLoader();
718         SimpleClass b = proxyFor(SimpleClass.class)
719                 .parentClassLoader(classLoaderB)
720                 .build();
721         assertEquals(classLoaderB, b.getClass().getClassLoader().getParent());
722 
723         assertTrue(a.getClass() != b.getClass());
724     }
725 
726     @Test
testAbstractClassWithUndeclaredInterfaceMethod()727     public void testAbstractClassWithUndeclaredInterfaceMethod() throws Throwable {
728         DeclaresInterface declaresInterface = proxyFor(DeclaresInterface.class)
729                 .build();
730         assertEquals("fake result", declaresInterface.call());
731         try {
732             ProxyBuilder.callSuper(declaresInterface, Callable.class.getMethod("call"));
733             fail();
734         } catch (IncompatibleClassChangeError expected) {
735         }
736     }
737 
738     public static abstract class DeclaresInterface implements Callable<String> {}
739 
740     @Test
testImplementingInterfaces()741     public void testImplementingInterfaces() throws Throwable {
742         SimpleClass simpleClass = proxyFor(SimpleClass.class)
743                 .implementing(Callable.class)
744                 .implementing(Comparable.class)
745                 .build();
746         assertEquals("fake result", simpleClass.simpleMethod());
747 
748         Callable<?> asCallable = (Callable<?>) simpleClass;
749         assertEquals("fake result", asCallable.call());
750 
751         Comparable<?> asComparable = (Comparable<?>) simpleClass;
752         fakeHandler.fakeResult = 3;
753         assertEquals(3, asComparable.compareTo(null));
754     }
755 
756     @Test
testCallSuperWithInterfaceMethod()757     public void testCallSuperWithInterfaceMethod() throws Throwable {
758         SimpleClass simpleClass = proxyFor(SimpleClass.class)
759                 .implementing(Callable.class)
760                 .build();
761         try {
762             ProxyBuilder.callSuper(simpleClass, Callable.class.getMethod("call"));
763             fail();
764         } catch (AbstractMethodError expected) {
765         } catch (NoSuchMethodError expected) {
766         }
767     }
768 
769     @Test
testImplementInterfaceCallingThroughConcreteClass()770     public void testImplementInterfaceCallingThroughConcreteClass() throws Throwable {
771         InvocationHandler invocationHandler = new InvocationHandler() {
772             public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
773                 assertEquals("a", ProxyBuilder.callSuper(o, method, objects));
774                 return "b";
775             }
776         };
777         ImplementsCallable proxy = proxyFor(ImplementsCallable.class)
778                 .implementing(Callable.class)
779                 .handler(invocationHandler)
780                 .build();
781         assertEquals("b", proxy.call());
782         assertEquals("a", ProxyBuilder.callSuper(
783                 proxy, ImplementsCallable.class.getMethod("call")));
784     }
785 
786     /**
787      * This test is a bit unintuitive because it exercises the synthetic methods
788      * that support covariant return types. Calling 'Object call()' on the
789      * interface bridges to 'String call()', and so the super method appears to
790      * also be proxied.
791      */
792     @Test
testImplementInterfaceCallingThroughInterface()793     public void testImplementInterfaceCallingThroughInterface() throws Throwable {
794         final AtomicInteger count = new AtomicInteger();
795 
796         InvocationHandler invocationHandler = new InvocationHandler() {
797             public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
798                 count.incrementAndGet();
799                 return ProxyBuilder.callSuper(o, method, objects);
800             }
801         };
802 
803         Callable<?> proxy = proxyFor(ImplementsCallable.class)
804                 .implementing(Callable.class)
805                 .handler(invocationHandler)
806                 .build();
807 
808         // the invocation handler is called twice!
809         assertEquals("a", proxy.call());
810         assertEquals(2, count.get());
811 
812         // the invocation handler is called, even though this is a callSuper() call!
813         assertEquals("a", ProxyBuilder.callSuper(proxy, Callable.class.getMethod("call")));
814         assertEquals(3, count.get());
815     }
816 
817     public static class ImplementsCallable implements Callable<String> {
call()818         public String call() throws Exception {
819             return "a";
820         }
821     }
822 
823     /**
824      * This test shows that our generated proxies follow the bytecode convention
825      * where methods can have the same name but unrelated return types. This is
826      * different from javac's convention where return types must be assignable
827      * in one direction or the other.
828      */
829     @Test
testInterfacesSameNamesDifferentReturnTypes()830     public void testInterfacesSameNamesDifferentReturnTypes() throws Throwable {
831         InvocationHandler handler = new InvocationHandler() {
832             public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
833                 if (method.getReturnType() == void.class) {
834                     return null;
835                 } else if (method.getReturnType() == String.class) {
836                     return "X";
837                 } else if (method.getReturnType() == int.class) {
838                     return 3;
839                 } else {
840                     throw new AssertionFailedError();
841                 }
842             }
843         };
844 
845         Object o = proxyFor(Object.class)
846                 .implementing(FooReturnsVoid.class, FooReturnsString.class, FooReturnsInt.class)
847                 .handler(handler)
848                 .build();
849 
850         FooReturnsVoid a = (FooReturnsVoid) o;
851         a.foo();
852 
853         FooReturnsString b = (FooReturnsString) o;
854         assertEquals("X", b.foo());
855 
856         FooReturnsInt c = (FooReturnsInt) o;
857         assertEquals(3, c.foo());
858     }
859 
860     @Test
testInterfacesSameNamesSameReturnType()861     public void testInterfacesSameNamesSameReturnType() throws Throwable {
862         Object o = proxyFor(Object.class)
863                 .implementing(FooReturnsInt.class, FooReturnsInt2.class)
864                 .build();
865 
866         fakeHandler.setFakeResult(3);
867 
868         FooReturnsInt a = (FooReturnsInt) o;
869         assertEquals(3, a.foo());
870 
871         FooReturnsInt2 b = (FooReturnsInt2) o;
872         assertEquals(3, b.foo());
873     }
874 
875     public interface FooReturnsVoid {
foo()876         void foo();
877     }
878 
879     public interface FooReturnsString {
foo()880         String foo();
881     }
882 
883     public interface FooReturnsInt {
foo()884         int foo();
885     }
886 
887     public interface FooReturnsInt2 {
foo()888         int foo();
889     }
890 
newPathClassLoader()891     private ClassLoader newPathClassLoader() throws Exception {
892         return (ClassLoader) Class.forName("dalvik.system.PathClassLoader")
893                 .getConstructor(String.class, ClassLoader.class)
894                 .newInstance("", getClass().getClassLoader());
895 
896     }
897 
898     @Test
testSubclassOfRandom()899     public void testSubclassOfRandom() throws Exception {
900         proxyFor(Random.class)
901                 .handler(new InvokeSuperHandler())
902                 .build();
903     }
904 
905     public static class FinalToString {
906         @Override
toString()907         public final String toString() {
908             return "no proxy";
909         }
910     }
911 
912     // https://code.google.com/p/dexmaker/issues/detail?id=12
913     @Test
testFinalToString()914     public void testFinalToString() throws Throwable {
915         assertEquals("no proxy", proxyFor(FinalToString.class).build().toString());
916     }
917 
918     public static class FinalInterfaceImpl implements FooReturnsString {
919         @Override
foo()920         public final String foo() {
921           return "no proxy";
922         }
923     }
924 
925     public static class ExtenstionOfFinalInterfaceImpl extends FinalInterfaceImpl
926             implements FooReturnsString {
927     }
928 
929     @Test
testFinalInterfaceImpl()930     public void testFinalInterfaceImpl() throws Throwable {
931         assertEquals("no proxy", proxyFor(ExtenstionOfFinalInterfaceImpl.class).build().foo());
932     }
933 
934     // https://code.google.com/p/dexmaker/issues/detail?id=9
935     public interface DeclaresMethodLate {
thisIsTheMethod()936         void thisIsTheMethod();
937     }
938 
939     public static class MakesMethodFinalEarly {
thisIsTheMethod()940         public final void thisIsTheMethod() {}
941     }
942 
943     public static class YouDoNotChooseYourFamily
944             extends MakesMethodFinalEarly implements DeclaresMethodLate {}
945 
946     @Test
testInterfaceMethodMadeFinalBeforeActualInheritance()947     public void testInterfaceMethodMadeFinalBeforeActualInheritance() throws Exception {
948         proxyFor(YouDoNotChooseYourFamily.class).build();
949     }
950 
951     public interface ExtendsAnotherInterface extends FooReturnsString {
952 
953     }
954 
955     @Test
testExtraInterfaceExtendsInterface()956     public void testExtraInterfaceExtendsInterface() throws Exception {
957         ExtendsAnotherInterface proxy = (ExtendsAnotherInterface)
958                 proxyFor(SimpleClass.class)
959                         .implementing(ExtendsAnotherInterface.class)
960                         .build();
961         fakeHandler.setFakeResult(ExtendsAnotherInterface.class.getName());
962         assertEquals(ExtendsAnotherInterface.class.getName(), proxy.foo());
963     }
964 
965     /** Simple helper to add the most common args for this test to the proxy builder. */
proxyFor(Class<T> clazz)966     private <T> ProxyBuilder<T> proxyFor(Class<T> clazz) throws Exception {
967         return ProxyBuilder.forClass(clazz)
968                 .handler(fakeHandler)
969                 .dexCache(DexMakerTest.getDataDirectory());
970     }
971 
972     private static class FakeInvocationHandler implements InvocationHandler {
973         private Object fakeResult = "fake result";
974 
invoke(Object proxy, Method method, Object[] args)975         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
976             return fakeResult;
977         }
978 
setFakeResult(Object result)979         public void setFakeResult(Object result) {
980             fakeResult = result;
981         }
982     }
983 
984     public static class TestOrderingClass {
returnsInt()985         public int returnsInt() {
986             return 0;
987         }
988 
returnsInt(int param1, int param2)989         public int returnsInt(int param1, int param2) {
990             return 1;
991         }
992 
returnsString()993         public String returnsString() {
994             return "string";
995         }
996 
returnsBoolean()997         public boolean returnsBoolean() {
998             return false;
999         }
1000 
returnsDouble()1001         public double returnsDouble() {
1002             return 1.0;
1003         }
1004 
returnsObject()1005         public Object returnsObject() {
1006             return new Object();
1007         }
1008     }
1009 
1010     @Test
1011     @SuppressWarnings("unchecked")
testMethodsGeneratedInDeterministicOrder()1012     public void testMethodsGeneratedInDeterministicOrder() throws Exception {
1013         // Grab the static methods array from the original class.
1014         Method[] methods1 = getMethodsForProxyClass(TestOrderingClass.class);
1015         assertNotNull(methods1);
1016 
1017         // Clear ProxyBuilder's in-memory cache of classes. This will force
1018         // it to rebuild the class and reset the static methods field.
1019         Map<Class<?>, Class<?>> map = getGeneratedProxyClasses();
1020         assertNotNull(map);
1021         map.clear();
1022 
1023         // Grab the static methods array from the rebuilt class.
1024         Method[] methods2 = getMethodsForProxyClass(TestOrderingClass.class);
1025         assertNotNull(methods2);
1026 
1027         // Ensure that the two method arrays are equal.
1028         assertTrue(Arrays.equals(methods1, methods2));
1029     }
1030 
1031     @Test
testOrderingClassWithDexMakerCaching()1032     public void testOrderingClassWithDexMakerCaching() throws Exception {
1033         doTestOrderClassWithDexMakerCaching();
1034 
1035         // Force ProxyBuilder to call DexMaker.generateAndLoad()
1036         getGeneratedProxyClasses().clear();
1037 
1038         doTestOrderClassWithDexMakerCaching();
1039     }
1040 
doTestOrderClassWithDexMakerCaching()1041     private void doTestOrderClassWithDexMakerCaching() throws Exception {
1042         TestOrderingClass proxy = ProxyBuilder.forClass(TestOrderingClass.class)
1043                 .handler(new InvokeSuperHandler())
1044                 .dexCache(DexMakerTest.getDataDirectory())
1045                 .build();
1046         assertEquals(0, proxy.returnsInt());
1047         assertEquals(1, proxy.returnsInt(1, 1));
1048         assertEquals("string", proxy.returnsString());
1049         assertFalse(proxy.returnsBoolean());
1050         assertEquals(1.0, proxy.returnsDouble(), DELTA_DOUBLE);
1051         assertNotNull(proxy.returnsObject());
1052         assertTrue(versionedDxDir.listFiles().length != 0);
1053     }
1054 
1055     // Returns static methods array from a proxy class.
getMethodsForProxyClass(Class<?> parentClass)1056     private Method[] getMethodsForProxyClass(Class<?> parentClass) throws Exception {
1057         Class<?> proxyClass = proxyFor(parentClass).buildProxyClass();
1058         Method[] methods = null;
1059         for (Field f : proxyClass.getDeclaredFields()) {
1060             if (Method[].class.isAssignableFrom(f.getType())) {
1061                 f.setAccessible(true);
1062                 methods = (Method[]) f.get(null);
1063                 break;
1064             }
1065         }
1066 
1067         return methods;
1068     }
1069 
getGeneratedProxyClasses()1070     private Map<Class<?>, Class<?>> getGeneratedProxyClasses() throws Exception {
1071         Field mapField = ProxyBuilder.class
1072                 .getDeclaredField("generatedProxyClasses");
1073         mapField.setAccessible(true);
1074         return (Map<Class<?>, Class<?>>) mapField.get(null);
1075     }
1076 
1077     public static class ConcreteClassA implements FooReturnsInt {
1078         // from FooReturnsInt
foo()1079         public int foo() {
1080             return 1;
1081         }
1082 
1083         // not from FooReturnsInt
bar()1084         public String bar() {
1085             return "bar";
1086         }
1087     }
1088 
1089     public static class ConcreteClassB implements FooReturnsInt {
1090         // from FooReturnsInt
foo()1091         public int foo() {
1092             return 0;
1093         }
1094 
1095         // not from FooReturnsInt
bar()1096         public String bar() {
1097             return "bahhr";
1098         }
1099     }
1100 
1101     @Test
testTwoClassesWithIdenticalMethodSignatures_DexMakerCaching()1102     public void testTwoClassesWithIdenticalMethodSignatures_DexMakerCaching() throws Exception {
1103         ConcreteClassA proxyA = ProxyBuilder.forClass(ConcreteClassA.class)
1104                 .handler(new InvokeSuperHandler())
1105                 .dexCache(DexMakerTest.getDataDirectory())
1106                 .build();
1107         assertEquals(1, proxyA.foo());
1108         assertEquals("bar", proxyA.bar());
1109         assertEquals(2, versionedDxDir.listFiles().length);
1110 
1111         ConcreteClassB proxyB = ProxyBuilder.forClass(ConcreteClassB.class)
1112                 .handler(new InvokeSuperHandler())
1113                 .dexCache(DexMakerTest.getDataDirectory())
1114                 .build();
1115         assertEquals(0, proxyB.foo());
1116         assertEquals("bahhr", proxyB.bar());
1117         assertEquals(4, versionedDxDir.listFiles().length);
1118     }
1119 }
1120