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