/* * Copyright (C) 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.inject; import static com.google.inject.Asserts.*; import static com.google.inject.name.Names.named; import com.google.common.base.Objects; import com.google.common.collect.Lists; import com.google.inject.name.Named; import com.google.inject.spi.Element; import com.google.inject.spi.Elements; import com.google.inject.util.Providers; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.logging.Logger; import junit.framework.TestCase; /** * A suite of tests for duplicate bindings. * * @author sameb@google.com (Sam Berlin) */ public class DuplicateBindingsTest extends TestCase { private FooImpl foo = new FooImpl(); private Provider<Foo> pFoo = Providers.<Foo>of(new FooImpl()); private Class<? extends Provider<? extends Foo>> pclFoo = FooProvider.class; private Class<? extends Foo> clFoo = FooImpl.class; private Constructor<FooImpl> cFoo = FooImpl.cxtor(); public void testDuplicateBindingsAreIgnored() { Injector injector = Guice.createInjector( new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo)); List<Key<?>> bindings = Lists.newArrayList(injector.getAllBindings().keySet()); removeBasicBindings(bindings); // Ensure only one binding existed for each type. assertTrue(bindings.remove(Key.get(Foo.class, named("instance")))); assertTrue(bindings.remove(Key.get(Foo.class, named("pInstance")))); assertTrue(bindings.remove(Key.get(Foo.class, named("pKey")))); assertTrue(bindings.remove(Key.get(Foo.class, named("linkedKey")))); assertTrue(bindings.remove(Key.get(FooImpl.class))); assertTrue(bindings.remove(Key.get(Foo.class, named("constructor")))); assertTrue(bindings.remove(Key.get(FooProvider.class))); // JIT binding assertTrue(bindings.remove(Key.get(Foo.class, named("providerMethod")))); assertEquals(bindings.toString(), 0, bindings.size()); } public void testElementsDeduplicate() { List<Element> elements = Elements.getElements( new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo)); assertEquals(14, elements.size()); assertEquals(7, new LinkedHashSet<Element>(elements).size()); } public void testProviderMethodsFailIfInstancesDiffer() { try { Guice.createInjector(new FailingProviderModule(), new FailingProviderModule()); fail("should have failed"); } catch (CreationException ce) { assertContains( ce.getMessage(), "A binding to " + Foo.class.getName() + " was already configured " + "at " + FailingProviderModule.class.getName(), "at " + FailingProviderModule.class.getName()); } } public void testSameScopeInstanceIgnored() { Guice.createInjector( new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo), new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo)); Guice.createInjector( new ScopedModule(Scopes.NO_SCOPE, foo, pFoo, pclFoo, clFoo, cFoo), new ScopedModule(Scopes.NO_SCOPE, foo, pFoo, pclFoo, clFoo, cFoo)); } public void testSameScopeAnnotationIgnored() { Guice.createInjector( new AnnotatedScopeModule(Singleton.class, foo, pFoo, pclFoo, clFoo, cFoo), new AnnotatedScopeModule(Singleton.class, foo, pFoo, pclFoo, clFoo, cFoo)); } public void testMixedAnnotationAndScopeForSingletonIgnored() { Guice.createInjector( new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo), new AnnotatedScopeModule(Singleton.class, foo, pFoo, pclFoo, clFoo, cFoo)); } public void testMixedScopeAndUnscopedIgnored() { Guice.createInjector( new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), new ScopedModule(Scopes.NO_SCOPE, foo, pFoo, pclFoo, clFoo, cFoo)); } public void testMixedScopeFails() { try { Guice.createInjector( new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo)); fail("expected exception"); } catch (CreationException ce) { String segment1 = "A binding to " + Foo.class.getName() + " annotated with " + named("pInstance") + " was already configured at " + SimpleModule.class.getName(); String segment2 = "A binding to " + Foo.class.getName() + " annotated with " + named("pKey") + " was already configured at " + SimpleModule.class.getName(); String segment3 = "A binding to " + Foo.class.getName() + " annotated with " + named("constructor") + " was already configured at " + SimpleModule.class.getName(); String segment4 = "A binding to " + FooImpl.class.getName() + " was already configured at " + SimpleModule.class.getName(); String atSegment = "at " + ScopedModule.class.getName(); if (isIncludeStackTraceOff()) { assertContains( ce.getMessage(), segment1, atSegment, segment2, atSegment, segment3, atSegment, segment4, atSegment); } else { assertContains( ce.getMessage(), segment1, atSegment, segment2, atSegment, segment4, atSegment, segment3, atSegment); } } } @SuppressWarnings("unchecked") public void testMixedTargetsFails() { try { Guice.createInjector( new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), new SimpleModule( new FooImpl(), Providers.<Foo>of(new FooImpl()), (Class) BarProvider.class, (Class) Bar.class, (Constructor) Bar.cxtor())); fail("expected exception"); } catch (CreationException ce) { assertContains( ce.getMessage(), "A binding to " + Foo.class.getName() + " annotated with " + named("pInstance") + " was already configured at " + SimpleModule.class.getName(), "at " + SimpleModule.class.getName(), "A binding to " + Foo.class.getName() + " annotated with " + named("pKey") + " was already configured at " + SimpleModule.class.getName(), "at " + SimpleModule.class.getName(), "A binding to " + Foo.class.getName() + " annotated with " + named("linkedKey") + " was already configured at " + SimpleModule.class.getName(), "at " + SimpleModule.class.getName(), "A binding to " + Foo.class.getName() + " annotated with " + named("constructor") + " was already configured at " + SimpleModule.class.getName(), "at " + SimpleModule.class.getName()); } } public void testExceptionInEqualsThrowsCreationException() { try { Guice.createInjector(new ThrowingModule(), new ThrowingModule()); fail("expected exception"); } catch (CreationException ce) { assertContains( ce.getMessage(), "A binding to " + Foo.class.getName() + " was already configured at " + ThrowingModule.class.getName(), "and an error was thrown while checking duplicate bindings. Error: java.lang.RuntimeException: Boo!", "at " + ThrowingModule.class.getName()); } } public void testChildInjectorDuplicateParentFail() { Injector injector = Guice.createInjector(new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo)); try { injector.createChildInjector(new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo)); fail("expected exception"); } catch (CreationException ce) { assertContains( ce.getMessage(), "A binding to " + Foo.class.getName() + " annotated with " + named("pInstance") + " was already configured at " + SimpleModule.class.getName(), "at " + SimpleModule.class.getName(), "A binding to " + Foo.class.getName() + " annotated with " + named("pKey") + " was already configured at " + SimpleModule.class.getName(), "at " + SimpleModule.class.getName(), "A binding to " + Foo.class.getName() + " annotated with " + named("linkedKey") + " was already configured at " + SimpleModule.class.getName(), "at " + SimpleModule.class.getName(), "A binding to " + Foo.class.getName() + " annotated with " + named("constructor") + " was already configured at " + SimpleModule.class.getName(), "at " + SimpleModule.class.getName(), "A binding to " + Foo.class.getName() + " annotated with " + named("providerMethod") + " was already configured at " + SimpleProviderModule.class.getName(), "at " + SimpleProviderModule.class.getName()); } } public void testDuplicatesSolelyInChildIgnored() { Injector injector = Guice.createInjector(); injector.createChildInjector( new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo)); } public void testDifferentBindingTypesFail() { List<Element> elements = Elements.getElements(new FailedModule(foo, pFoo, pclFoo, clFoo, cFoo)); // Make sure every combination of the elements with another element fails. // This ensures that duplication checks the kind of binding also. for (Element e1 : elements) { for (Element e2 : elements) { // if they're the same, this shouldn't fail. try { Guice.createInjector(Elements.getModule(Arrays.asList(e1, e2))); if (e1 != e2) { fail("must fail!"); } } catch (CreationException expected) { if (e1 != e2) { assertContains( expected.getMessage(), "A binding to " + Foo.class.getName() + " was already configured at " + FailedModule.class.getName(), "at " + FailedModule.class.getName()); } else { throw expected; } } } } } public void testJitBindingsAreCheckedAfterConversions() { Guice.createInjector( new AbstractModule() { @Override protected void configure() { bind(A.class); bind(A.class).to(RealA.class); } }); } public void testEqualsNotCalledByDefaultOnInstance() { final HashEqualsTester a = new HashEqualsTester(); a.throwOnEquals = true; Guice.createInjector( new AbstractModule() { @Override protected void configure() { bind(String.class); bind(HashEqualsTester.class).toInstance(a); } }); } public void testEqualsNotCalledByDefaultOnProvider() { final HashEqualsTester a = new HashEqualsTester(); a.throwOnEquals = true; Guice.createInjector( new AbstractModule() { @Override protected void configure() { bind(String.class); bind(Object.class).toProvider(a); } }); } public void testHashcodeNeverCalledOnInstance() { final HashEqualsTester a = new HashEqualsTester(); a.throwOnHashcode = true; a.equality = "test"; final HashEqualsTester b = new HashEqualsTester(); b.throwOnHashcode = true; b.equality = "test"; Guice.createInjector( new AbstractModule() { @Override protected void configure() { bind(String.class); bind(HashEqualsTester.class).toInstance(a); bind(HashEqualsTester.class).toInstance(b); } }); } public void testHashcodeNeverCalledOnProviderInstance() { final HashEqualsTester a = new HashEqualsTester(); a.throwOnHashcode = true; a.equality = "test"; final HashEqualsTester b = new HashEqualsTester(); b.throwOnHashcode = true; b.equality = "test"; Guice.createInjector( new AbstractModule() { @Override protected void configure() { bind(String.class); bind(Object.class).toProvider(a); bind(Object.class).toProvider(b); } }); } private static class RealA extends A {} @ImplementedBy(RealA.class) private static class A {} private void removeBasicBindings(Collection<Key<?>> bindings) { bindings.remove(Key.get(Injector.class)); bindings.remove(Key.get(Logger.class)); bindings.remove(Key.get(Stage.class)); } private static class ThrowingModule extends AbstractModule { @Override protected void configure() { bind(Foo.class) .toInstance( new Foo() { @Override public boolean equals(Object obj) { throw new RuntimeException("Boo!"); } }); } } private abstract static class FooModule extends AbstractModule { protected final FooImpl foo; protected final Provider<Foo> pFoo; protected final Class<? extends Provider<? extends Foo>> pclFoo; protected final Class<? extends Foo> clFoo; protected final Constructor<FooImpl> cFoo; FooModule( FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo, Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo) { this.foo = foo; this.pFoo = pFoo; this.pclFoo = pclFoo; this.clFoo = clFoo; this.cFoo = cFoo; } } 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) { super(foo, pFoo, pclFoo, clFoo, cFoo); } @Override protected void configure() { // InstanceBinding bind(Foo.class).toInstance(foo); // ProviderInstanceBinding bind(Foo.class).toProvider(pFoo); // ProviderKeyBinding bind(Foo.class).toProvider(pclFoo); // LinkedKeyBinding bind(Foo.class).to(clFoo); // ConstructorBinding bind(Foo.class).toConstructor(cFoo); } @Provides Foo foo() { return null; } } private static class FailingProviderModule extends AbstractModule { @Override protected void configure() {} @Provides Foo foo() { return null; } } private static class SimpleProviderModule extends AbstractModule { @Override protected void configure() {} @Provides @Named("providerMethod") Foo foo() { return null; } @Override public boolean equals(Object obj) { return obj.getClass() == getClass(); } } 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) { super(foo, pFoo, pclFoo, clFoo, cFoo); } @Override protected void configure() { // InstanceBinding bind(Foo.class).annotatedWith(named("instance")).toInstance(foo); // ProviderInstanceBinding bind(Foo.class).annotatedWith(named("pInstance")).toProvider(pFoo); // ProviderKeyBinding bind(Foo.class).annotatedWith(named("pKey")).toProvider(pclFoo); // LinkedKeyBinding bind(Foo.class).annotatedWith(named("linkedKey")).to(clFoo); // UntargettedBinding / ConstructorBinding bind(FooImpl.class); // ConstructorBinding bind(Foo.class).annotatedWith(named("constructor")).toConstructor(cFoo); // ProviderMethod // (reconstructed from an Element to ensure it doesn't get filtered out // by deduplicating Modules) install(Elements.getModule(Elements.getElements(new SimpleProviderModule()))); } } private static class ScopedModule extends FooModule { private final Scope scope; ScopedModule( Scope scope, FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo, Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo) { super(foo, pFoo, pclFoo, clFoo, cFoo); this.scope = scope; } @Override protected void configure() { // ProviderInstanceBinding bind(Foo.class).annotatedWith(named("pInstance")).toProvider(pFoo).in(scope); // ProviderKeyBinding bind(Foo.class).annotatedWith(named("pKey")).toProvider(pclFoo).in(scope); // LinkedKeyBinding bind(Foo.class).annotatedWith(named("linkedKey")).to(clFoo).in(scope); // UntargettedBinding / ConstructorBinding bind(FooImpl.class).in(scope); // ConstructorBinding bind(Foo.class).annotatedWith(named("constructor")).toConstructor(cFoo).in(scope); } } private static class AnnotatedScopeModule extends FooModule { private final Class<? extends Annotation> scope; AnnotatedScopeModule( Class<? extends Annotation> scope, FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo, Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo) { super(foo, pFoo, pclFoo, clFoo, cFoo); this.scope = scope; } @Override protected void configure() { // ProviderInstanceBinding bind(Foo.class).annotatedWith(named("pInstance")).toProvider(pFoo).in(scope); // ProviderKeyBinding bind(Foo.class).annotatedWith(named("pKey")).toProvider(pclFoo).in(scope); // LinkedKeyBinding bind(Foo.class).annotatedWith(named("linkedKey")).to(clFoo).in(scope); // UntargettedBinding / ConstructorBinding bind(FooImpl.class).in(scope); // ConstructorBinding bind(Foo.class).annotatedWith(named("constructor")).toConstructor(cFoo).in(scope); } } private static interface Foo {} private static class FooImpl implements Foo { @Inject public FooImpl() {} private static Constructor<FooImpl> cxtor() { try { return FooImpl.class.getConstructor(); } catch (SecurityException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } } private static class FooProvider implements Provider<Foo> { @Override public Foo get() { return new FooImpl(); } } private static class Bar implements Foo { @Inject public Bar() {} private static Constructor<Bar> cxtor() { try { return Bar.class.getConstructor(); } catch (SecurityException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } } private static class BarProvider implements Provider<Foo> { @Override public Foo get() { return new Bar(); } } private static class HashEqualsTester implements Provider<Object> { private String equality; private boolean throwOnEquals; private boolean throwOnHashcode; @Override public boolean equals(Object obj) { if (throwOnEquals) { throw new RuntimeException(); } else if (obj instanceof HashEqualsTester) { HashEqualsTester o = (HashEqualsTester) obj; if (o.throwOnEquals) { throw new RuntimeException(); } if (equality == null && o.equality == null) { return this == o; } else { return Objects.equal(equality, o.equality); } } else { return false; } } @Override public int hashCode() { if (throwOnHashcode) { throw new RuntimeException(); } else { return super.hashCode(); } } @Override public Object get() { return new Object(); } } }