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