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
17package com.google.inject;
18
19import static com.google.inject.Asserts.*;
20import static com.google.inject.name.Names.named;
21
22import com.google.common.base.Objects;
23import com.google.common.collect.Lists;
24import com.google.inject.name.Named;
25import com.google.inject.spi.Element;
26import com.google.inject.spi.Elements;
27import com.google.inject.util.Providers;
28
29import junit.framework.TestCase;
30
31import java.lang.annotation.Annotation;
32import java.lang.reflect.Constructor;
33import java.util.Arrays;
34import java.util.Collection;
35import java.util.LinkedHashSet;
36import java.util.List;
37import java.util.logging.Logger;
38
39/**
40 * A suite of tests for duplicate bindings.
41 *
42 * @author sameb@google.com (Sam Berlin)
43 */
44public 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