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