Home | History | Annotate | Download | only in inject
      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 (at) 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 
     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 
     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 
     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 
     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 
    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 
    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 
    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 
    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")
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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
    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 
    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 {
    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 
    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 
    384     @Provides Foo foo() {
    385       return null;
    386     }
    387   }
    388 
    389   private static class FailingProviderModule extends AbstractModule {
    390     @Override protected void configure() {}
    391 
    392     @Provides Foo foo() {
    393       return null;
    394     }
    395   }
    396 
    397   private static class SimpleProviderModule extends AbstractModule {
    398     @Override protected void configure() {}
    399 
    400     @Provides @Named("providerMethod") Foo foo() {
    401       return null;
    402     }
    403 
    404     @Override
    405     public boolean equals(Object obj) {
    406       return obj.getClass() == getClass();
    407     }
    408   }
    409 
    410   private static class SimpleModule extends FooModule {
    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 
    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 
    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 
    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 
    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 
    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 {
    501     @Inject public FooImpl() {}
    502 
    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> {
    514     public Foo get() {
    515       return new FooImpl();
    516     }
    517   }
    518 
    519   private static class Bar implements Foo {
    520     @Inject public Bar() {}
    521 
    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> {
    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
    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
    563     public int hashCode() {
    564       if(throwOnHashcode) {
    565         throw new RuntimeException();
    566       } else {
    567         return super.hashCode();
    568       }
    569     }
    570 
    571     public Object get() {
    572       return new Object();
    573     }
    574   }
    575 
    576 }
    577