ProxyBuilderTest.java revision b4fdb175545f178c642194bc43a3fad31af3f0e9
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
17package com.google.dexmaker.stock;
18
19import com.google.dexmaker.DexMakerTest;
20import java.io.File;
21import java.io.IOException;
22import java.lang.reflect.InvocationHandler;
23import java.lang.reflect.Method;
24import java.lang.reflect.UndeclaredThrowableException;
25import java.util.Random;
26import junit.framework.AssertionFailedError;
27import junit.framework.TestCase;
28
29public class ProxyBuilderTest extends TestCase {
30    private FakeInvocationHandler fakeHandler = new FakeInvocationHandler();
31
32    public static class SimpleClass {
33        public String simpleMethod() {
34            throw new AssertionFailedError();
35        }
36    }
37
38    public void testExampleOperation() throws Throwable {
39        fakeHandler.setFakeResult("expected");
40        SimpleClass proxy = proxyFor(SimpleClass.class).build();
41        assertEquals("expected", proxy.simpleMethod());
42    }
43
44    public static class ConstructorTakesArguments {
45        private final String argument;
46
47        public ConstructorTakesArguments(String arg) {
48            argument = arg;
49        }
50
51        public String method() {
52            throw new AssertionFailedError();
53        }
54    }
55
56    public void testConstruction_SucceedsIfCorrectArgumentsProvided() throws Throwable {
57        ConstructorTakesArguments proxy = proxyFor(ConstructorTakesArguments.class)
58                .constructorArgTypes(String.class)
59                .constructorArgValues("hello")
60                .build();
61        assertEquals("hello", proxy.argument);
62        proxy.method();
63    }
64
65    public void testConstruction_FailsWithWrongNumberOfArguments() throws Throwable {
66        try {
67            proxyFor(ConstructorTakesArguments.class).build();
68            fail();
69        } catch (IllegalArgumentException expected) {}
70    }
71
72    public void testClassIsNotAccessbile_FailsWithUnsupportedOperationException() throws Exception {
73        class MethodVisibilityClass {
74        }
75        try {
76            proxyFor(MethodVisibilityClass.class).build();
77            fail();
78        } catch (UnsupportedOperationException expected) {}
79    }
80
81    private static class PrivateVisibilityClass {
82    }
83
84    public void testPrivateClass_FailsWithUnsupportedOperationException() throws Exception {
85        try {
86            proxyFor(PrivateVisibilityClass.class).build();
87            fail();
88        } catch (UnsupportedOperationException expected) {}
89    }
90
91    protected static class ProtectedVisibilityClass {
92        public String foo() {
93            throw new AssertionFailedError();
94        }
95    }
96
97    public void testProtectedVisibility_WorksFine() throws Exception {
98        assertEquals("fake result", proxyFor(ProtectedVisibilityClass.class).build().foo());
99    }
100
101    public static class HasFinalMethod {
102        public String nonFinalMethod() {
103            return "non-final method";
104        }
105
106        public final String finalMethod() {
107            return "final method";
108        }
109    }
110
111    public void testCanProxyClassesWithFinalMethods_WillNotCallTheFinalMethod() throws Throwable {
112        HasFinalMethod proxy = proxyFor(HasFinalMethod.class).build();
113        assertEquals("final method", proxy.finalMethod());
114        assertEquals("fake result", proxy.nonFinalMethod());
115    }
116
117    public static class HasPrivateMethod {
118        private String result() {
119            return "expected";
120        }
121    }
122
123    public void testProxyingPrivateMethods_NotIntercepted() throws Throwable {
124        assertEquals("expected", proxyFor(HasPrivateMethod.class).build().result());
125    }
126
127    public static class HasPackagePrivateMethod {
128        String result() {
129            throw new AssertionFailedError();
130        }
131    }
132
133    public void testProxyingPackagePrivateMethods_AreIntercepted() throws Throwable {
134        assertEquals("fake result", proxyFor(HasPackagePrivateMethod.class).build().result());
135    }
136
137    public static class HasProtectedMethod {
138        protected String result() {
139            throw new AssertionFailedError();
140        }
141    }
142
143    public void testProxyingProtectedMethods_AreIntercepted() throws Throwable {
144        assertEquals("fake result", proxyFor(HasProtectedMethod.class).build().result());
145    }
146
147    public static class HasVoidMethod {
148        public void dangerousMethod() {
149            fail();
150        }
151    }
152
153    public void testVoidMethod_ShouldNotThrowRuntimeException() throws Throwable {
154        proxyFor(HasVoidMethod.class).build().dangerousMethod();
155    }
156
157    public void testObjectMethodsAreAlsoProxied() throws Throwable {
158        Object proxy = proxyFor(Object.class).build();
159        fakeHandler.setFakeResult("mystring");
160        assertEquals("mystring", proxy.toString());
161        fakeHandler.setFakeResult(-1);
162        assertEquals(-1, proxy.hashCode());
163        fakeHandler.setFakeResult(false);
164        assertEquals(false, proxy.equals(proxy));
165    }
166
167    public static class AllPrimitiveMethods {
168        public boolean getBoolean() { return true; }
169        public int getInt() { return 1; }
170        public byte getByte() { return 2; }
171        public long getLong() { return 3L; }
172        public short getShort() { return 4; }
173        public float getFloat() { return 5f; }
174        public double getDouble() { return 6.0; }
175        public char getChar() { return 'c'; }
176    }
177
178    public void testAllPrimitiveReturnTypes() throws Throwable {
179        AllPrimitiveMethods proxy = proxyFor(AllPrimitiveMethods.class).build();
180        fakeHandler.setFakeResult(false);
181        assertEquals(false, proxy.getBoolean());
182        fakeHandler.setFakeResult(8);
183        assertEquals(8, proxy.getInt());
184        fakeHandler.setFakeResult((byte) 9);
185        assertEquals(9, proxy.getByte());
186        fakeHandler.setFakeResult(10L);
187        assertEquals(10, proxy.getLong());
188        fakeHandler.setFakeResult((short) 11);
189        assertEquals(11, proxy.getShort());
190        fakeHandler.setFakeResult(12f);
191        assertEquals(12f, proxy.getFloat());
192        fakeHandler.setFakeResult(13.0);
193        assertEquals(13.0, proxy.getDouble());
194        fakeHandler.setFakeResult('z');
195        assertEquals('z', proxy.getChar());
196    }
197
198    public static class PassThroughAllPrimitives {
199        public boolean getBoolean(boolean input) { return input; }
200        public int getInt(int input) { return input; }
201        public byte getByte(byte input) { return input; }
202        public long getLong(long input) { return input; }
203        public short getShort(short input) { return input; }
204        public float getFloat(float input) { return input; }
205        public double getDouble(double input) { return input; }
206        public char getChar(char input) { return input; }
207        public String getString(String input) { return input; }
208        public Object getObject(Object input) { return input; }
209        public void getNothing() {}
210    }
211
212    public static class InvokeSuperHandler implements InvocationHandler {
213        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
214            return ProxyBuilder.callSuper(proxy, method, args);
215        }
216    }
217
218    public void testPassThroughWorksForAllPrimitives() throws Exception {
219        PassThroughAllPrimitives proxy = proxyFor(PassThroughAllPrimitives.class)
220                .handler(new InvokeSuperHandler())
221                .build();
222        assertEquals(false, proxy.getBoolean(false));
223        assertEquals(true, proxy.getBoolean(true));
224        assertEquals(0, proxy.getInt(0));
225        assertEquals(1, proxy.getInt(1));
226        assertEquals((byte) 2, proxy.getByte((byte) 2));
227        assertEquals((byte) 3, proxy.getByte((byte) 3));
228        assertEquals(4L, proxy.getLong(4L));
229        assertEquals(5L, proxy.getLong(5L));
230        assertEquals((short) 6, proxy.getShort((short) 6));
231        assertEquals((short) 7, proxy.getShort((short) 7));
232        assertEquals(8f, proxy.getFloat(8f));
233        assertEquals(9f, proxy.getFloat(9f));
234        assertEquals(10.0, proxy.getDouble(10.0));
235        assertEquals(11.0, proxy.getDouble(11.0));
236        assertEquals('a', proxy.getChar('a'));
237        assertEquals('b', proxy.getChar('b'));
238        assertEquals("asdf", proxy.getString("asdf"));
239        assertEquals("qwer", proxy.getString("qwer"));
240        assertEquals(null, proxy.getString(null));
241        Object a = new Object();
242        assertEquals(a, proxy.getObject(a));
243        assertEquals(null, proxy.getObject(null));
244        proxy.getNothing();
245    }
246
247    public static class ExtendsAllPrimitiveMethods extends AllPrimitiveMethods {
248        public int example() { return 0; }
249    }
250
251    public void testProxyWorksForSuperclassMethodsAlso() throws Throwable {
252        ExtendsAllPrimitiveMethods proxy = proxyFor(ExtendsAllPrimitiveMethods.class).build();
253        fakeHandler.setFakeResult(99);
254        assertEquals(99, proxy.example());
255        assertEquals(99, proxy.getInt());
256        assertEquals(99, proxy.hashCode());
257    }
258
259    public static class HasOddParams {
260        public long method(int first, Integer second) {
261            throw new AssertionFailedError();
262        }
263    }
264
265    public void testMixingBoxedAndUnboxedParams() throws Throwable {
266        HasOddParams proxy = proxyFor(HasOddParams.class).build();
267        fakeHandler.setFakeResult(99L);
268        assertEquals(99L, proxy.method(1, Integer.valueOf(2)));
269    }
270
271    public static class SingleInt {
272        public String getString(int value) {
273            throw new AssertionFailedError();
274        }
275    }
276
277    public void testSinglePrimitiveParameter() throws Throwable {
278        InvocationHandler handler = new InvocationHandler() {
279            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
280                return "asdf" + ((Integer) args[0]).intValue();
281            }
282        };
283        assertEquals("asdf1", proxyFor(SingleInt.class).handler(handler).build().getString(1));
284    }
285
286    public static class TwoConstructors {
287        private final String string;
288
289        public TwoConstructors() {
290            string = "no-arg";
291        }
292
293        public TwoConstructors(boolean unused) {
294            string = "one-arg";
295        }
296    }
297
298    public void testNoConstructorArguments_CallsNoArgConstructor() throws Throwable {
299        TwoConstructors twoConstructors = proxyFor(TwoConstructors.class).build();
300        assertEquals("no-arg", twoConstructors.string);
301    }
302
303    public void testWithoutInvocationHandler_ThrowsIllegalArgumentException() throws Throwable {
304        try {
305            ProxyBuilder.forClass(TwoConstructors.class)
306                    .dexCache(DexMakerTest.getDataDirectory())
307                    .build();
308            fail();
309        } catch (IllegalArgumentException expected) {}
310    }
311
312    public static class HardToConstructCorrectly {
313        public HardToConstructCorrectly() { fail(); }
314        public HardToConstructCorrectly(Runnable ignored) { fail(); }
315        public HardToConstructCorrectly(Exception ignored) { fail(); }
316        public HardToConstructCorrectly(Boolean ignored) { /* safe */ }
317        public HardToConstructCorrectly(Integer ignored) { fail(); }
318    }
319
320    public void testHardToConstruct_WorksIfYouSpecifyTheConstructorCorrectly() throws Throwable {
321        proxyFor(HardToConstructCorrectly.class)
322                .constructorArgTypes(Boolean.class)
323                .constructorArgValues(true)
324                .build();
325    }
326
327    public void testHardToConstruct_EvenWorksWhenArgsAreAmbiguous() throws Throwable {
328        proxyFor(HardToConstructCorrectly.class)
329                .constructorArgTypes(Boolean.class)
330                .constructorArgValues(new Object[] { null })
331                .build();
332    }
333
334    public void testHardToConstruct_DoesNotInferTypesFromValues() throws Throwable {
335        try {
336            proxyFor(HardToConstructCorrectly.class)
337                    .constructorArgValues(true)
338                    .build();
339            fail();
340        } catch (IllegalArgumentException expected) {}
341    }
342
343    public void testDefaultProxyHasSuperMethodToAccessOriginal() throws Exception {
344        Object objectProxy = proxyFor(Object.class).build();
345        assertNotNull(objectProxy.getClass().getMethod("super_hashCode"));
346    }
347
348    public static class PrintsOddAndValue {
349        public String method(int value) {
350            return "odd " + value;
351        }
352    }
353
354    public void testSometimesDelegateToSuper() throws Exception {
355        InvocationHandler delegatesOddValues = new InvocationHandler() {
356            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
357                if (method.getName().equals("method")) {
358                    int intValue = ((Integer) args[0]).intValue();
359                    if (intValue % 2 == 0) {
360                        return "even " + intValue;
361                    }
362                }
363                return ProxyBuilder.callSuper(proxy, method, args);
364            }
365        };
366        PrintsOddAndValue proxy = proxyFor(PrintsOddAndValue.class)
367                .handler(delegatesOddValues)
368                .build();
369        assertEquals("even 0", proxy.method(0));
370        assertEquals("odd 1", proxy.method(1));
371        assertEquals("even 2", proxy.method(2));
372        assertEquals("odd 3", proxy.method(3));
373    }
374
375    public static class DoubleReturn {
376        public double getValue() {
377            return 2.0;
378        }
379    }
380
381    public void testUnboxedResult() throws Exception {
382        fakeHandler.fakeResult = 2.0;
383        assertEquals(2.0, proxyFor(DoubleReturn.class).build().getValue());
384    }
385
386    public static void staticMethod() {
387    }
388
389    public void testDoesNotOverrideStaticMethods() throws Exception {
390        // Method should exist on this test class itself.
391        ProxyBuilderTest.class.getDeclaredMethod("staticMethod");
392        // Method should not exist on the subclass.
393        try {
394            proxyFor(ProxyBuilderTest.class).build().getClass().getDeclaredMethod("staticMethod");
395            fail();
396        } catch (NoSuchMethodException expected) {}
397    }
398
399    public void testIllegalCacheDirectory() throws Exception {
400        try {
401          proxyFor(ProxyForIllegalCacheDirectory.class)
402                  .dexCache(new File("/poop/"))
403                  .build();
404          fail();
405        } catch (IOException expected) {
406        }
407    }
408
409    public static class ProxyForIllegalCacheDirectory {
410    }
411
412    public void testInvalidConstructorSpecification() throws Exception {
413        try {
414            proxyFor(Object.class)
415                    .constructorArgTypes(String.class, Boolean.class)
416                    .constructorArgValues("asdf", true)
417                    .build();
418            fail();
419        } catch (IllegalArgumentException expected) {}
420    }
421
422    public static abstract class AbstractClass {
423        public abstract Object getValue();
424    }
425
426    public void testAbstractClassBehaviour() throws Exception {
427        assertEquals("fake result", proxyFor(AbstractClass.class).build().getValue());
428    }
429
430    public static class CtorHasDeclaredException {
431        public CtorHasDeclaredException() throws IOException {
432            throw new IOException();
433        }
434    }
435
436    public static class CtorHasRuntimeException {
437        public CtorHasRuntimeException() {
438            throw new RuntimeException("my message");
439        }
440    }
441
442    public static class CtorHasError {
443        public CtorHasError() {
444            throw new Error("my message again");
445        }
446    }
447
448    public void testParentConstructorThrowsDeclaredException() throws Exception {
449        try {
450            proxyFor(CtorHasDeclaredException.class).build();
451            fail();
452        } catch (UndeclaredThrowableException expected) {
453            assertTrue(expected.getCause() instanceof IOException);
454        }
455        try {
456            proxyFor(CtorHasRuntimeException.class).build();
457            fail();
458        } catch (RuntimeException expected) {
459            assertEquals("my message", expected.getMessage());
460        }
461        try {
462            proxyFor(CtorHasError.class).build();
463            fail();
464        } catch (Error expected) {
465            assertEquals("my message again", expected.getMessage());
466        }
467    }
468
469    public void testGetInvocationHandler_NormalOperation() throws Exception {
470        Object proxy = proxyFor(Object.class).build();
471        assertSame(fakeHandler, ProxyBuilder.getInvocationHandler(proxy));
472    }
473
474    public void testGetInvocationHandler_NotAProxy() {
475        try {
476            ProxyBuilder.getInvocationHandler(new Object());
477            fail();
478        } catch (IllegalArgumentException expected) {}
479    }
480
481    public static class ReturnsObject {
482        public Object getValue() {
483            return new Object();
484        }
485    }
486
487    public static class ReturnsString extends ReturnsObject {
488        @Override
489        public String getValue() {
490            return "a string";
491        }
492    }
493
494    public void testCovariantReturnTypes_NormalBehaviour() throws Exception {
495        String expected = "some string";
496        fakeHandler.setFakeResult(expected);
497        assertSame(expected, proxyFor(ReturnsObject.class).build().getValue());
498        assertSame(expected, proxyFor(ReturnsString.class).build().getValue());
499    }
500
501    public void testCovariantReturnTypes_WrongReturnType() throws Exception {
502        try {
503            fakeHandler.setFakeResult(new Object());
504            proxyFor(ReturnsString.class).build().getValue();
505            fail();
506        } catch (ClassCastException expected) {}
507    }
508
509    public void testCaching() throws Exception {
510        SimpleClass a = proxyFor(SimpleClass.class).build();
511        SimpleClass b = proxyFor(SimpleClass.class).build();
512        assertSame(a.getClass(), b.getClass());
513    }
514
515    public void testCachingWithMultipleConstructors() throws Exception {
516        HasMultipleConstructors a = ProxyBuilder.forClass(HasMultipleConstructors.class)
517                .constructorArgTypes()
518                .constructorArgValues()
519                .handler(fakeHandler)
520                .dexCache(DexMakerTest.getDataDirectory()).build();
521        assertEquals("no args", a.calledConstructor);
522        HasMultipleConstructors b = ProxyBuilder.forClass(HasMultipleConstructors.class)
523                .constructorArgTypes(int.class)
524                .constructorArgValues(2)
525                .handler(fakeHandler)
526                .dexCache(DexMakerTest.getDataDirectory()).build();
527        assertEquals("int 2", b.calledConstructor);
528        assertEquals(a.getClass(), b.getClass());
529
530        HasMultipleConstructors c = ProxyBuilder.forClass(HasMultipleConstructors.class)
531                .constructorArgTypes(Integer.class)
532                .constructorArgValues(3)
533                .handler(fakeHandler)
534                .dexCache(DexMakerTest.getDataDirectory()).build();
535        assertEquals("Integer 3", c.calledConstructor);
536        assertEquals(a.getClass(), c.getClass());
537    }
538
539    public static class HasMultipleConstructors {
540        private final String calledConstructor;
541        public HasMultipleConstructors() {
542            calledConstructor = "no args";
543        }
544        public HasMultipleConstructors(int b) {
545            calledConstructor = "int " + b;
546        }
547        public HasMultipleConstructors(Integer c) {
548            calledConstructor = "Integer " + c;
549        }
550    }
551
552    public void testClassNotCachedWithDifferentParentClassLoaders() throws Exception {
553        ClassLoader classLoaderA = newPathClassLoader();
554        SimpleClass a = proxyFor(SimpleClass.class)
555                .parentClassLoader(classLoaderA)
556                .build();
557        assertEquals(classLoaderA, a.getClass().getClassLoader().getParent());
558
559        ClassLoader classLoaderB = newPathClassLoader();
560        SimpleClass b = proxyFor(SimpleClass.class)
561                .parentClassLoader(classLoaderB)
562                .build();
563        assertEquals(classLoaderB, b.getClass().getClassLoader().getParent());
564
565        assertTrue(a.getClass() != b.getClass());
566    }
567
568    private ClassLoader newPathClassLoader() throws Exception {
569        return (ClassLoader) Class.forName("dalvik.system.PathClassLoader")
570                .getConstructor(String.class, ClassLoader.class)
571                .newInstance("", getClass().getClassLoader());
572
573    }
574
575    public void testSubclassOfRandom() throws Exception {
576        proxyFor(Random.class)
577                .handler(new InvokeSuperHandler())
578                .build();
579    }
580
581    /** Simple helper to add the most common args for this test to the proxy builder. */
582    private <T> ProxyBuilder<T> proxyFor(Class<T> clazz) throws Exception {
583        return ProxyBuilder.forClass(clazz)
584                .handler(fakeHandler)
585                .dexCache(DexMakerTest.getDataDirectory());
586    }
587
588    private static class FakeInvocationHandler implements InvocationHandler {
589        private Object fakeResult = "fake result";
590
591        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
592            return fakeResult;
593        }
594
595        public void setFakeResult(Object result) {
596            fakeResult = result;
597        }
598    }
599}
600