1 /** 2 * Copyright (C) 2010 Google Inc. 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.google.inject; 18 19 import static com.google.inject.Asserts.*; 20 import static com.google.inject.name.Names.named; 21 22 import com.google.common.base.Objects; 23 import com.google.common.collect.Lists; 24 import com.google.inject.name.Named; 25 import com.google.inject.spi.Element; 26 import com.google.inject.spi.Elements; 27 import com.google.inject.util.Providers; 28 29 import junit.framework.TestCase; 30 31 import java.lang.annotation.Annotation; 32 import java.lang.reflect.Constructor; 33 import java.util.Arrays; 34 import java.util.Collection; 35 import java.util.LinkedHashSet; 36 import java.util.List; 37 import java.util.logging.Logger; 38 39 /** 40 * A suite of tests for duplicate bindings. 41 * 42 * @author sameb@google.com (Sam Berlin) 43 */ 44 public class DuplicateBindingsTest extends TestCase { 45 46 private FooImpl foo = new FooImpl(); 47 private Provider<Foo> pFoo = Providers.<Foo>of(new FooImpl()); 48 private Class<? extends Provider<? extends Foo>> pclFoo = FooProvider.class; 49 private Class<? extends Foo> clFoo = FooImpl.class; 50 private Constructor<FooImpl> cFoo = FooImpl.cxtor(); 51 testDuplicateBindingsAreIgnored()52 public void testDuplicateBindingsAreIgnored() { 53 Injector injector = Guice.createInjector( 54 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), 55 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo) 56 ); 57 List<Key<?>> bindings = Lists.newArrayList(injector.getAllBindings().keySet()); 58 removeBasicBindings(bindings); 59 60 // Ensure only one binding existed for each type. 61 assertTrue(bindings.remove(Key.get(Foo.class, named("instance")))); 62 assertTrue(bindings.remove(Key.get(Foo.class, named("pInstance")))); 63 assertTrue(bindings.remove(Key.get(Foo.class, named("pKey")))); 64 assertTrue(bindings.remove(Key.get(Foo.class, named("linkedKey")))); 65 assertTrue(bindings.remove(Key.get(FooImpl.class))); 66 assertTrue(bindings.remove(Key.get(Foo.class, named("constructor")))); 67 assertTrue(bindings.remove(Key.get(FooProvider.class))); // JIT binding 68 assertTrue(bindings.remove(Key.get(Foo.class, named("providerMethod")))); 69 70 assertEquals(bindings.toString(), 0, bindings.size()); 71 } 72 testElementsDeduplicate()73 public void testElementsDeduplicate() { 74 List<Element> elements = Elements.getElements( 75 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), 76 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo) 77 ); 78 assertEquals(14, elements.size()); 79 assertEquals(7, new LinkedHashSet<Element>(elements).size()); 80 } 81 testProviderMethodsFailIfInstancesDiffer()82 public void testProviderMethodsFailIfInstancesDiffer() { 83 try { 84 Guice.createInjector(new FailingProviderModule(), new FailingProviderModule()); 85 fail("should have failed"); 86 } catch(CreationException ce) { 87 assertContains(ce.getMessage(), 88 "A binding to " + Foo.class.getName() + " was already configured " + 89 "at " + FailingProviderModule.class.getName(), 90 "at " + FailingProviderModule.class.getName() 91 ); 92 } 93 } 94 testSameScopeInstanceIgnored()95 public void testSameScopeInstanceIgnored() { 96 Guice.createInjector( 97 new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo), 98 new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo) 99 ); 100 101 Guice.createInjector( 102 new ScopedModule(Scopes.NO_SCOPE, foo, pFoo, pclFoo, clFoo, cFoo), 103 new ScopedModule(Scopes.NO_SCOPE, foo, pFoo, pclFoo, clFoo, cFoo) 104 ); 105 } 106 testSameScopeAnnotationIgnored()107 public void testSameScopeAnnotationIgnored() { 108 Guice.createInjector( 109 new AnnotatedScopeModule(Singleton.class, foo, pFoo, pclFoo, clFoo, cFoo), 110 new AnnotatedScopeModule(Singleton.class, foo, pFoo, pclFoo, clFoo, cFoo) 111 ); 112 } 113 testMixedAnnotationAndScopeForSingletonIgnored()114 public void testMixedAnnotationAndScopeForSingletonIgnored() { 115 Guice.createInjector( 116 new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo), 117 new AnnotatedScopeModule(Singleton.class, foo, pFoo, pclFoo, clFoo, cFoo) 118 ); 119 } 120 testMixedScopeAndUnscopedIgnored()121 public void testMixedScopeAndUnscopedIgnored() { 122 Guice.createInjector( 123 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), 124 new ScopedModule(Scopes.NO_SCOPE, foo, pFoo, pclFoo, clFoo, cFoo) 125 ); 126 } 127 testMixedScopeFails()128 public void testMixedScopeFails() { 129 try { 130 Guice.createInjector( 131 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), 132 new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo) 133 ); 134 fail("expected exception"); 135 } catch(CreationException ce) { 136 String segment1 = "A binding to " + Foo.class.getName() + " annotated with " 137 + named("pInstance") + " was already configured at " + SimpleModule.class.getName(); 138 String segment2 = "A binding to " + Foo.class.getName() + " annotated with " + named("pKey") 139 + " was already configured at " + SimpleModule.class.getName(); 140 String segment3 = "A binding to " + Foo.class.getName() + " annotated with " 141 + named("constructor") + " was already configured at " + SimpleModule.class.getName(); 142 String segment4 = "A binding to " + FooImpl.class.getName() + " was already configured at " 143 + SimpleModule.class.getName(); 144 String atSegment = "at " + ScopedModule.class.getName(); 145 if (isIncludeStackTraceOff()) { 146 assertContains(ce.getMessage(), segment1 , atSegment, segment2, atSegment, segment3, 147 atSegment, segment4, atSegment); 148 } else { 149 assertContains(ce.getMessage(), segment1 , atSegment, segment2, atSegment, segment4, 150 atSegment, segment3, atSegment); 151 } 152 } 153 } 154 155 @SuppressWarnings("unchecked") testMixedTargetsFails()156 public void testMixedTargetsFails() { 157 try { 158 Guice.createInjector( 159 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), 160 new SimpleModule(new FooImpl(), Providers.<Foo>of(new FooImpl()), 161 (Class)BarProvider.class, (Class)Bar.class, (Constructor)Bar.cxtor()) 162 ); 163 fail("expected exception"); 164 } catch(CreationException ce) { 165 assertContains(ce.getMessage(), 166 "A binding to " + Foo.class.getName() + " annotated with " + named("pInstance") + " was already configured at " + SimpleModule.class.getName(), 167 "at " + SimpleModule.class.getName(), 168 "A binding to " + Foo.class.getName() + " annotated with " + named("pKey") + " was already configured at " + SimpleModule.class.getName(), 169 "at " + SimpleModule.class.getName(), 170 "A binding to " + Foo.class.getName() + " annotated with " + named("linkedKey") + " was already configured at " + SimpleModule.class.getName(), 171 "at " + SimpleModule.class.getName(), 172 "A binding to " + Foo.class.getName() + " annotated with " + named("constructor") + " was already configured at " + SimpleModule.class.getName(), 173 "at " + SimpleModule.class.getName()); 174 } 175 } 176 testExceptionInEqualsThrowsCreationException()177 public void testExceptionInEqualsThrowsCreationException() { 178 try { 179 Guice.createInjector(new ThrowingModule(), new ThrowingModule()); 180 fail("expected exception"); 181 } catch(CreationException ce) { 182 assertContains(ce.getMessage(), 183 "A binding to " + Foo.class.getName() + " was already configured at " + ThrowingModule.class.getName(), 184 "and an error was thrown while checking duplicate bindings. Error: java.lang.RuntimeException: Boo!", 185 "at " + ThrowingModule.class.getName()); 186 } 187 } 188 testChildInjectorDuplicateParentFail()189 public void testChildInjectorDuplicateParentFail() { 190 Injector injector = Guice.createInjector( 191 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo) 192 ); 193 194 try { 195 injector.createChildInjector( 196 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo) 197 ); 198 fail("expected exception"); 199 } catch(CreationException ce) { 200 assertContains(ce.getMessage(), 201 "A binding to " + Foo.class.getName() + " annotated with " + named("pInstance") + " was already configured at " + SimpleModule.class.getName(), 202 "at " + SimpleModule.class.getName(), 203 "A binding to " + Foo.class.getName() + " annotated with " + named("pKey") + " was already configured at " + SimpleModule.class.getName(), 204 "at " + SimpleModule.class.getName(), 205 "A binding to " + Foo.class.getName() + " annotated with " + named("linkedKey") + " was already configured at " + SimpleModule.class.getName(), 206 "at " + SimpleModule.class.getName(), 207 "A binding to " + Foo.class.getName() + " annotated with " + named("constructor") + " was already configured at " + SimpleModule.class.getName(), 208 "at " + SimpleModule.class.getName(), 209 "A binding to " + Foo.class.getName() + " annotated with " + named("providerMethod") + " was already configured at " + SimpleProviderModule.class.getName(), 210 "at " + SimpleProviderModule.class.getName() 211 ); 212 } 213 214 215 } 216 testDuplicatesSolelyInChildIgnored()217 public void testDuplicatesSolelyInChildIgnored() { 218 Injector injector = Guice.createInjector(); 219 injector.createChildInjector( 220 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), 221 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo) 222 ); 223 } 224 testDifferentBindingTypesFail()225 public void testDifferentBindingTypesFail() { 226 List<Element> elements = Elements.getElements( 227 new FailedModule(foo, pFoo, pclFoo, clFoo, cFoo) 228 ); 229 230 // Make sure every combination of the elements with another element fails. 231 // This ensures that duplication checks the kind of binding also. 232 for(Element e1 : elements) { 233 for(Element e2: elements) { 234 // if they're the same, this shouldn't fail. 235 try { 236 Guice.createInjector(Elements.getModule(Arrays.asList(e1, e2))); 237 if(e1 != e2) { 238 fail("must fail!"); 239 } 240 } catch(CreationException expected) { 241 if(e1 != e2) { 242 assertContains(expected.getMessage(), 243 "A binding to " + Foo.class.getName() + " was already configured at " + FailedModule.class.getName(), 244 "at " + FailedModule.class.getName()); 245 } else { 246 throw expected; 247 } 248 } 249 } 250 } 251 } 252 testJitBindingsAreCheckedAfterConversions()253 public void testJitBindingsAreCheckedAfterConversions() { 254 Guice.createInjector(new AbstractModule() { 255 @Override 256 protected void configure() { 257 bind(A.class); 258 bind(A.class).to(RealA.class); 259 } 260 }); 261 } 262 testEqualsNotCalledByDefaultOnInstance()263 public void testEqualsNotCalledByDefaultOnInstance() { 264 final HashEqualsTester a = new HashEqualsTester(); 265 a.throwOnEquals = true; 266 Guice.createInjector(new AbstractModule() { 267 @Override 268 protected void configure() { 269 bind(String.class); 270 bind(HashEqualsTester.class).toInstance(a); 271 } 272 }); 273 } 274 testEqualsNotCalledByDefaultOnProvider()275 public void testEqualsNotCalledByDefaultOnProvider() { 276 final HashEqualsTester a = new HashEqualsTester(); 277 a.throwOnEquals = true; 278 Guice.createInjector(new AbstractModule() { 279 @Override 280 protected void configure() { 281 bind(String.class); 282 bind(Object.class).toProvider(a); 283 } 284 }); 285 } 286 testHashcodeNeverCalledOnInstance()287 public void testHashcodeNeverCalledOnInstance() { 288 final HashEqualsTester a = new HashEqualsTester(); 289 a.throwOnHashcode = true; 290 a.equality = "test"; 291 292 final HashEqualsTester b = new HashEqualsTester(); 293 b.throwOnHashcode = true; 294 b.equality = "test"; 295 Guice.createInjector(new AbstractModule() { 296 @Override 297 protected void configure() { 298 bind(String.class); 299 bind(HashEqualsTester.class).toInstance(a); 300 bind(HashEqualsTester.class).toInstance(b); 301 } 302 }); 303 } 304 testHashcodeNeverCalledOnProviderInstance()305 public void testHashcodeNeverCalledOnProviderInstance() { 306 final HashEqualsTester a = new HashEqualsTester(); 307 a.throwOnHashcode = true; 308 a.equality = "test"; 309 310 final HashEqualsTester b = new HashEqualsTester(); 311 b.throwOnHashcode = true; 312 b.equality = "test"; 313 Guice.createInjector(new AbstractModule() { 314 @Override 315 protected void configure() { 316 bind(String.class); 317 bind(Object.class).toProvider(a); 318 bind(Object.class).toProvider(b); 319 } 320 }); 321 } 322 323 private static class RealA extends A {} 324 @ImplementedBy(RealA.class) private static class A {} 325 removeBasicBindings(Collection<Key<?>> bindings)326 private void removeBasicBindings(Collection<Key<?>> bindings) { 327 bindings.remove(Key.get(Injector.class)); 328 bindings.remove(Key.get(Logger.class)); 329 bindings.remove(Key.get(Stage.class)); 330 } 331 332 private static class ThrowingModule extends AbstractModule { 333 @Override configure()334 protected void configure() { 335 bind(Foo.class).toInstance(new Foo() { 336 @Override 337 public boolean equals(Object obj) { 338 throw new RuntimeException("Boo!"); 339 } 340 }); 341 } 342 } 343 344 private static abstract class FooModule extends AbstractModule { 345 protected final FooImpl foo; 346 protected final Provider<Foo> pFoo; 347 protected final Class<? extends Provider<? extends Foo>> pclFoo; 348 protected final Class<? extends Foo> clFoo; 349 protected final Constructor<FooImpl> cFoo; 350 FooModule(FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo, Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo)351 FooModule(FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo, 352 Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo) { 353 this.foo = foo; 354 this.pFoo = pFoo; 355 this.pclFoo = pclFoo; 356 this.clFoo = clFoo; 357 this.cFoo = cFoo; 358 } 359 } 360 361 private static class FailedModule extends FooModule { FailedModule(FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo, Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo)362 FailedModule(FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo, 363 Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo) { 364 super(foo, pFoo, pclFoo, clFoo, cFoo); 365 } 366 configure()367 protected void configure() { 368 // InstanceBinding 369 bind(Foo.class).toInstance(foo); 370 371 // ProviderInstanceBinding 372 bind(Foo.class).toProvider(pFoo); 373 374 // ProviderKeyBinding 375 bind(Foo.class).toProvider(pclFoo); 376 377 // LinkedKeyBinding 378 bind(Foo.class).to(clFoo); 379 380 // ConstructorBinding 381 bind(Foo.class).toConstructor(cFoo); 382 } 383 foo()384 @Provides Foo foo() { 385 return null; 386 } 387 } 388 389 private static class FailingProviderModule extends AbstractModule { configure()390 @Override protected void configure() {} 391 foo()392 @Provides Foo foo() { 393 return null; 394 } 395 } 396 397 private static class SimpleProviderModule extends AbstractModule { configure()398 @Override protected void configure() {} 399 foo()400 @Provides @Named("providerMethod") Foo foo() { 401 return null; 402 } 403 404 @Override equals(Object obj)405 public boolean equals(Object obj) { 406 return obj.getClass() == getClass(); 407 } 408 } 409 410 private static class SimpleModule extends FooModule { SimpleModule(FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo, Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo)411 SimpleModule(FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo, 412 Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo) { 413 super(foo, pFoo, pclFoo, clFoo, cFoo); 414 } 415 configure()416 protected void configure() { 417 // InstanceBinding 418 bind(Foo.class).annotatedWith(named("instance")).toInstance(foo); 419 420 // ProviderInstanceBinding 421 bind(Foo.class).annotatedWith(named("pInstance")).toProvider(pFoo); 422 423 // ProviderKeyBinding 424 bind(Foo.class).annotatedWith(named("pKey")).toProvider(pclFoo); 425 426 // LinkedKeyBinding 427 bind(Foo.class).annotatedWith(named("linkedKey")).to(clFoo); 428 429 // UntargettedBinding / ConstructorBinding 430 bind(FooImpl.class); 431 432 // ConstructorBinding 433 bind(Foo.class).annotatedWith(named("constructor")).toConstructor(cFoo); 434 435 // ProviderMethod 436 // (reconstructed from an Element to ensure it doesn't get filtered out 437 // by deduplicating Modules) 438 install(Elements.getModule(Elements.getElements(new SimpleProviderModule()))); 439 } 440 } 441 442 private static class ScopedModule extends FooModule { 443 private final Scope scope; 444 ScopedModule(Scope scope, FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo, Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo)445 ScopedModule(Scope scope, FooImpl foo, Provider<Foo> pFoo, 446 Class<? extends Provider<? extends Foo>> pclFoo, Class<? extends Foo> clFoo, 447 Constructor<FooImpl> cFoo) { 448 super(foo, pFoo, pclFoo, clFoo, cFoo); 449 this.scope = scope; 450 } 451 configure()452 protected void configure() { 453 // ProviderInstanceBinding 454 bind(Foo.class).annotatedWith(named("pInstance")).toProvider(pFoo).in(scope); 455 456 // ProviderKeyBinding 457 bind(Foo.class).annotatedWith(named("pKey")).toProvider(pclFoo).in(scope); 458 459 // LinkedKeyBinding 460 bind(Foo.class).annotatedWith(named("linkedKey")).to(clFoo).in(scope); 461 462 // UntargettedBinding / ConstructorBinding 463 bind(FooImpl.class).in(scope); 464 465 // ConstructorBinding 466 bind(Foo.class).annotatedWith(named("constructor")).toConstructor(cFoo).in(scope); 467 } 468 } 469 470 private static class AnnotatedScopeModule extends FooModule { 471 private final Class<? extends Annotation> scope; 472 AnnotatedScopeModule(Class<? extends Annotation> scope, FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo, Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo)473 AnnotatedScopeModule(Class<? extends Annotation> scope, FooImpl foo, Provider<Foo> pFoo, 474 Class<? extends Provider<? extends Foo>> pclFoo, Class<? extends Foo> clFoo, 475 Constructor<FooImpl> cFoo) { 476 super(foo, pFoo, pclFoo, clFoo, cFoo); 477 this.scope = scope; 478 } 479 480 configure()481 protected void configure() { 482 // ProviderInstanceBinding 483 bind(Foo.class).annotatedWith(named("pInstance")).toProvider(pFoo).in(scope); 484 485 // ProviderKeyBinding 486 bind(Foo.class).annotatedWith(named("pKey")).toProvider(pclFoo).in(scope); 487 488 // LinkedKeyBinding 489 bind(Foo.class).annotatedWith(named("linkedKey")).to(clFoo).in(scope); 490 491 // UntargettedBinding / ConstructorBinding 492 bind(FooImpl.class).in(scope); 493 494 // ConstructorBinding 495 bind(Foo.class).annotatedWith(named("constructor")).toConstructor(cFoo).in(scope); 496 } 497 } 498 499 private static interface Foo {} 500 private static class FooImpl implements Foo { FooImpl()501 @Inject public FooImpl() {} 502 cxtor()503 private static Constructor<FooImpl> cxtor() { 504 try { 505 return FooImpl.class.getConstructor(); 506 } catch (SecurityException e) { 507 throw new RuntimeException(e); 508 } catch (NoSuchMethodException e) { 509 throw new RuntimeException(e); 510 } 511 } 512 } 513 private static class FooProvider implements Provider<Foo> { get()514 public Foo get() { 515 return new FooImpl(); 516 } 517 } 518 519 private static class Bar implements Foo { Bar()520 @Inject public Bar() {} 521 cxtor()522 private static Constructor<Bar> cxtor() { 523 try { 524 return Bar.class.getConstructor(); 525 } catch (SecurityException e) { 526 throw new RuntimeException(e); 527 } catch (NoSuchMethodException e) { 528 throw new RuntimeException(e); 529 } 530 } 531 } 532 private static class BarProvider implements Provider<Foo> { get()533 public Foo get() { 534 return new Bar(); 535 } 536 } 537 538 private static class HashEqualsTester implements Provider<Object> { 539 private String equality; 540 private boolean throwOnEquals; 541 private boolean throwOnHashcode; 542 543 @Override equals(Object obj)544 public boolean equals(Object obj) { 545 if (throwOnEquals) { 546 throw new RuntimeException(); 547 } else if (obj instanceof HashEqualsTester) { 548 HashEqualsTester o = (HashEqualsTester)obj; 549 if(o.throwOnEquals) { 550 throw new RuntimeException(); 551 } 552 if(equality == null && o.equality == null) { 553 return this == o; 554 } else { 555 return Objects.equal(equality, o.equality); 556 } 557 } else { 558 return false; 559 } 560 } 561 562 @Override hashCode()563 public int hashCode() { 564 if(throwOnHashcode) { 565 throw new RuntimeException(); 566 } else { 567 return super.hashCode(); 568 } 569 } 570 get()571 public Object get() { 572 return new Object(); 573 } 574 } 575 576 } 577