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