/** * Copyright (C) 2006 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.asModuleChain; import static com.google.inject.Asserts.assertContains; import static com.google.inject.Asserts.getDeclaringSourcePart; import static com.google.inject.name.Names.named; import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.inject.name.Named; import com.google.inject.spi.Element; import com.google.inject.spi.Elements; import com.google.inject.spi.PrivateElements; import com.google.inject.util.Providers; import junit.framework.TestCase; import java.io.IOException; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; /** * @author crazybob@google.com (Bob Lee) */ public class ScopesTest extends TestCase { static final long DEADLOCK_TIMEOUT_SECONDS = 1; private final AbstractModule singletonsModule = new AbstractModule() { @Override protected void configure() { bind(BoundAsSingleton.class).in(Scopes.SINGLETON); bind(AnnotatedSingleton.class); bind(EagerSingleton.class).asEagerSingleton(); bind(LinkedSingleton.class).to(RealLinkedSingleton.class); bind(DependsOnJustInTimeSingleton.class); bind(NotASingleton.class); bind(ImplementedBySingleton.class).in(Scopes.SINGLETON); bind(ProvidedBySingleton.class).in(Scopes.SINGLETON); } }; @Override protected void setUp() throws Exception { AnnotatedSingleton.nextInstanceId = 0; BoundAsSingleton.nextInstanceId = 0; EagerSingleton.nextInstanceId = 0; RealLinkedSingleton.nextInstanceId = 0; JustInTimeSingleton.nextInstanceId = 0; NotASingleton.nextInstanceId = 0; Implementation.nextInstanceId = 0; ProvidedBySingleton.nextInstanceId = 0; ThrowingSingleton.nextInstanceId = 0; } public void testSingletons() { Injector injector = Guice.createInjector(singletonsModule); assertSame( injector.getInstance(BoundAsSingleton.class), injector.getInstance(BoundAsSingleton.class)); assertSame( injector.getInstance(AnnotatedSingleton.class), injector.getInstance(AnnotatedSingleton.class)); assertSame( injector.getInstance(EagerSingleton.class), injector.getInstance(EagerSingleton.class)); assertSame( injector.getInstance(LinkedSingleton.class), injector.getInstance(LinkedSingleton.class)); assertSame( injector.getInstance(JustInTimeSingleton.class), injector.getInstance(JustInTimeSingleton.class)); assertNotSame( injector.getInstance(NotASingleton.class), injector.getInstance(NotASingleton.class)); assertSame( injector.getInstance(ImplementedBySingleton.class), injector.getInstance(ImplementedBySingleton.class)); assertSame( injector.getInstance(ProvidedBySingleton.class), injector.getInstance(ProvidedBySingleton.class)); } public void testJustInTimeAnnotatedSingleton() { Injector injector = Guice.createInjector(); assertSame( injector.getInstance(AnnotatedSingleton.class), injector.getInstance(AnnotatedSingleton.class)); } public void testSingletonIsPerInjector() { assertNotSame( Guice.createInjector().getInstance(AnnotatedSingleton.class), Guice.createInjector().getInstance(AnnotatedSingleton.class)); } public void testOverriddingAnnotation() { Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bind(AnnotatedSingleton.class).in(Scopes.NO_SCOPE); } }); assertNotSame( injector.getInstance(AnnotatedSingleton.class), injector.getInstance(AnnotatedSingleton.class)); } public void testScopingAnnotationsOnAbstractTypeViaBind() { try { Guice.createInjector(new AbstractModule() { @Override protected void configure() { bind(A.class).to(AImpl.class); } }); fail(); } catch (CreationException expected) { assertContains(expected.getMessage(), A.class.getName() + " is annotated with " + Singleton.class.getName(), "but scope annotations are not supported for abstract types.", "at " + A.class.getName() + ".class(ScopesTest.java:"); } } @Singleton interface A {} static class AImpl implements A {} @Retention(RUNTIME) @interface Component {} @Component @Singleton interface ComponentAnnotationTest {} static class ComponentAnnotationTestImpl implements ComponentAnnotationTest {} public void testScopingAnnotationsOnAbstractTypeIsValidForComponent() { Guice.createInjector(new AbstractModule() { @Override protected void configure() { bind(ComponentAnnotationTest.class).to(ComponentAnnotationTestImpl.class); } }); } public void testScopingAnnotationsOnAbstractTypeViaImplementedBy() { try { Guice.createInjector().getInstance(D.class); fail(); } catch (ConfigurationException expected) { assertContains(expected.getMessage(), D.class.getName() + " is annotated with " + Singleton.class.getName(), "but scope annotations are not supported for abstract types.", "at " + D.class.getName() + ".class(ScopesTest.java:"); } } @Singleton @ImplementedBy(DImpl.class) interface D {} static class DImpl implements D {} public void testScopingAnnotationsOnAbstractTypeViaProvidedBy() { try { Guice.createInjector().getInstance(E.class); fail(); } catch (ConfigurationException expected) { assertContains(expected.getMessage(), E.class.getName() + " is annotated with " + Singleton.class.getName(), "but scope annotations are not supported for abstract types.", "at " + E.class.getName() + ".class(ScopesTest.java:"); } } @Singleton @ProvidedBy(EProvider.class) interface E {} static class EProvider implements Provider { public E get() { return null; } } public void testScopeUsedButNotBound() { try { Guice.createInjector(new AbstractModule() { @Override protected void configure() { bind(B.class).in(CustomScoped.class); bind(C.class); } }); fail(); } catch (CreationException expected) { assertContains(expected.getMessage(), "1) No scope is bound to " + CustomScoped.class.getName(), "at " + getClass().getName(), getDeclaringSourcePart(getClass()), "2) No scope is bound to " + CustomScoped.class.getName(), "at " + C.class.getName() + ".class"); } } static class B {} @CustomScoped static class C {} public void testSingletonsInProductionStage() { Guice.createInjector(Stage.PRODUCTION, singletonsModule); assertEquals(1, AnnotatedSingleton.nextInstanceId); assertEquals(1, BoundAsSingleton.nextInstanceId); assertEquals(1, EagerSingleton.nextInstanceId); assertEquals(1, RealLinkedSingleton.nextInstanceId); assertEquals(1, JustInTimeSingleton.nextInstanceId); assertEquals(0, NotASingleton.nextInstanceId); } public void testSingletonsInDevelopmentStage() { Guice.createInjector(Stage.DEVELOPMENT, singletonsModule); assertEquals(0, AnnotatedSingleton.nextInstanceId); assertEquals(0, BoundAsSingleton.nextInstanceId); assertEquals(1, EagerSingleton.nextInstanceId); assertEquals(0, RealLinkedSingleton.nextInstanceId); assertEquals(0, JustInTimeSingleton.nextInstanceId); assertEquals(0, NotASingleton.nextInstanceId); } public void testSingletonScopeIsNotSerializable() throws IOException { Asserts.assertNotSerializable(Scopes.SINGLETON); } public void testNoScopeIsNotSerializable() throws IOException { Asserts.assertNotSerializable(Scopes.NO_SCOPE); } public void testUnscopedProviderWorksOutsideOfRequestedScope() { final RememberProviderScope scope = new RememberProviderScope(); Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bindScope(CustomScoped.class, scope); bind(List.class).to(ArrayList.class).in(CustomScoped.class); } }); injector.getInstance(List.class); Provider listProvider = scope.providers.get(Key.get(List.class)); // this line fails with a NullPointerException because the Providers // passed to Scope.scope() don't work outside of the scope() method. assertTrue(listProvider.get() instanceof ArrayList); } static class OuterRuntimeModule extends AbstractModule { @Override protected void configure() { install(new InnerRuntimeModule()); } } static class InnerRuntimeModule extends AbstractModule { @Override protected void configure() { bindScope(NotRuntimeRetainedScoped.class, Scopes.NO_SCOPE); } } public void testScopeAnnotationWithoutRuntimeRetention() { try { Guice.createInjector(new OuterRuntimeModule()); fail(); } catch (CreationException expected) { assertContains(expected.getMessage(), "1) Please annotate " + NotRuntimeRetainedScoped.class.getName() + " with @Retention(RUNTIME).", "at " + InnerRuntimeModule.class.getName() + getDeclaringSourcePart(getClass()), asModuleChain(OuterRuntimeModule.class, InnerRuntimeModule.class)); } } static class OuterDeprecatedModule extends AbstractModule { @Override protected void configure() { install(new InnerDeprecatedModule()); } } static class InnerDeprecatedModule extends AbstractModule { @Override protected void configure() { bindScope(Deprecated.class, Scopes.NO_SCOPE); } } public void testBindScopeToAnnotationWithoutScopeAnnotation() { try { Guice.createInjector(new OuterDeprecatedModule()); fail(); } catch (CreationException expected) { assertContains(expected.getMessage(), "1) Please annotate " + Deprecated.class.getName() + " with @ScopeAnnotation.", "at " + InnerDeprecatedModule.class.getName() + getDeclaringSourcePart(getClass()), asModuleChain(OuterDeprecatedModule.class, InnerDeprecatedModule.class)); } } static class OuterScopeModule extends AbstractModule { @Override protected void configure() { install(new CustomNoScopeModule()); install(new CustomSingletonModule()); } } static class CustomNoScopeModule extends AbstractModule { @Override protected void configure() { bindScope(CustomScoped.class, Scopes.NO_SCOPE); } } static class CustomSingletonModule extends AbstractModule { @Override protected void configure() { bindScope(CustomScoped.class, Scopes.SINGLETON); } } public void testBindScopeTooManyTimes() { try { Guice.createInjector(new OuterScopeModule()); fail(); } catch (CreationException expected) { assertContains(expected.getMessage(), "1) Scope Scopes.NO_SCOPE is already bound to " + CustomScoped.class.getName() + " at " + CustomNoScopeModule.class.getName() + getDeclaringSourcePart(getClass()), asModuleChain(OuterScopeModule.class, CustomNoScopeModule.class), "Cannot bind Scopes.SINGLETON.", "at " + ScopesTest.class.getName(), getDeclaringSourcePart(getClass()), asModuleChain(OuterScopeModule.class, CustomSingletonModule.class)); } } public void testBindDuplicateScope() { Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bindScope(CustomScoped.class, Scopes.SINGLETON); bindScope(CustomScoped.class, Scopes.SINGLETON); } }); assertSame( injector.getInstance(AnnotatedCustomScoped.class), injector.getInstance(AnnotatedCustomScoped.class)); } public void testDuplicateScopeAnnotations() { Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bindScope(CustomScoped.class, Scopes.NO_SCOPE); } }); try { injector.getInstance(SingletonAndCustomScoped.class); fail(); } catch (ConfigurationException expected) { assertContains(expected.getMessage(), "1) More than one scope annotation was found: ", "while locating " + SingletonAndCustomScoped.class.getName()); } } public void testNullScopedAsASingleton() { Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() {} final Iterator values = Arrays.asList(null, "A").iterator(); @Provides @Singleton String provideString() { return values.next(); } }); assertNull(injector.getInstance(String.class)); assertNull(injector.getInstance(String.class)); assertNull(injector.getInstance(String.class)); } class RememberProviderScope implements Scope { final Map, Provider> providers = Maps.newHashMap(); public Provider scope(Key key, Provider unscoped) { providers.put(key, unscoped); return unscoped; } } public void testSingletonAnnotationOnParameterizedType() { Injector injector = Guice.createInjector(); assertSame(injector.getInstance(new Key>() {}), injector.getInstance(new Key>() {})); assertSame(injector.getInstance(new Key>() {}), injector.getInstance(new Key>() {})); } @ImplementedBy(Injected.class) public interface In {} @Singleton public static class Injected implements In {} @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RUNTIME) @ScopeAnnotation public @interface CustomScoped {} static final Scope CUSTOM_SCOPE = new Scope() { public Provider scope(Key key, Provider unscoped) { return Scopes.SINGLETON.scope(key, unscoped); } }; @Target({ ElementType.TYPE, ElementType.METHOD }) @ScopeAnnotation public @interface NotRuntimeRetainedScoped {} @CustomScoped static class AnnotatedCustomScoped {} @Singleton static class AnnotatedSingleton { static int nextInstanceId; final int instanceId = nextInstanceId++; } static class BoundAsSingleton { static int nextInstanceId; final int instanceId = nextInstanceId++; } static class EagerSingleton { static int nextInstanceId; final int instanceId = nextInstanceId++; } interface LinkedSingleton {} @Singleton static class RealLinkedSingleton implements LinkedSingleton { static int nextInstanceId; final int instanceId = nextInstanceId++; } static class DependsOnJustInTimeSingleton { @Inject JustInTimeSingleton justInTimeSingleton; } @Singleton static class JustInTimeSingleton { static int nextInstanceId; final int instanceId = nextInstanceId++; } static class NotASingleton { static int nextInstanceId; final int instanceId = nextInstanceId++; } @SuppressWarnings("MoreThanOneScopeAnnotationOnClass") // suppress compiler error for testing @Singleton @CustomScoped static class SingletonAndCustomScoped {} @ImplementedBy(Implementation.class) static interface ImplementedBySingleton {} @ProvidedBy(ImplementationProvider.class) static class ProvidedBySingleton { static int nextInstanceId; final int instanceId = nextInstanceId++; } static class Implementation implements ImplementedBySingleton { static int nextInstanceId; final int instanceId = nextInstanceId++; } static class ImplementationProvider implements Provider { public ProvidedBySingleton get() { return new ProvidedBySingleton(); } } public void testScopeThatGetsAnUnrelatedObject() { Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bind(B.class); bind(C.class); ProviderGetScope providerGetScope = new ProviderGetScope(); requestInjection(providerGetScope); bindScope(CustomScoped.class, providerGetScope); } }); injector.getInstance(C.class); } class ProviderGetScope implements Scope { @Inject Provider bProvider; public Provider scope(Key key, final Provider unscoped) { return new Provider() { public T get() { bProvider.get(); return unscoped.get(); } }; } } public void testIsSingletonPositive() { final Key a = Key.get(String.class, named("A")); final Key b = Key.get(String.class, named("B")); final Key c = Key.get(String.class, named("C")); final Key d = Key.get(String.class, named("D")); final Key e = Key.get(String.class, named("E")); final Key f = Key.get(String.class, named("F")); final Key g = Key.get(String.class, named("G")); final Key h = Key.get(Object.class, named("H")); final Key i = Key.get(String.class, named("I")); Module singletonBindings = new AbstractModule() { @Override protected void configure() { bind(a).to(b); bind(b).to(c); bind(c).toProvider(Providers.of("c")).in(Scopes.SINGLETON); bind(d).toInstance("d"); bind(e).toProvider(Providers.of("e")).asEagerSingleton(); bind(f).toProvider(Providers.of("f")).in(Singleton.class); bind(h).to(AnnotatedSingleton.class); install(new PrivateModule() { @Override protected void configure() { bind(i).toProvider(Providers.of("i")).in(Singleton.class); expose(i); } }); } @Provides @Named("G") @Singleton String provideG() { return "g"; } }; @SuppressWarnings("unchecked") // we know the module contains only bindings List moduleBindings = Elements.getElements(singletonBindings); ImmutableMap, Binding> map = indexBindings(moduleBindings); assertFalse(Scopes.isSingleton(map.get(a))); // linked bindings are not followed by modules assertFalse(Scopes.isSingleton(map.get(b))); assertTrue(Scopes.isSingleton(map.get(c))); assertTrue(Scopes.isSingleton(map.get(d))); assertTrue(Scopes.isSingleton(map.get(e))); assertTrue(Scopes.isSingleton(map.get(f))); assertTrue(Scopes.isSingleton(map.get(g))); assertFalse(Scopes.isSingleton(map.get(h))); // annotated classes are not followed by modules assertTrue(Scopes.isSingleton(map.get(i))); Injector injector = Guice.createInjector(singletonBindings); assertTrue(Scopes.isSingleton(injector.getBinding(a))); assertTrue(Scopes.isSingleton(injector.getBinding(b))); assertTrue(Scopes.isSingleton(injector.getBinding(c))); assertTrue(Scopes.isSingleton(injector.getBinding(d))); assertTrue(Scopes.isSingleton(injector.getBinding(e))); assertTrue(Scopes.isSingleton(injector.getBinding(f))); assertTrue(Scopes.isSingleton(injector.getBinding(g))); assertTrue(Scopes.isSingleton(injector.getBinding(h))); assertTrue(Scopes.isSingleton(injector.getBinding(i))); } public void testIsSingletonNegative() { final Key a = Key.get(String.class, named("A")); final Key b = Key.get(String.class, named("B")); final Key c = Key.get(String.class, named("C")); final Key d = Key.get(String.class, named("D")); final Key e = Key.get(String.class, named("E")); final Key f = Key.get(String.class, named("F")); Module singletonBindings = new AbstractModule() { @Override protected void configure() { bind(a).to(b); bind(b).to(c); bind(c).toProvider(Providers.of("c")).in(Scopes.NO_SCOPE); bind(d).toProvider(Providers.of("d")).in(CustomScoped.class); bindScope(CustomScoped.class, Scopes.NO_SCOPE); install(new PrivateModule() { @Override protected void configure() { bind(f).toProvider(Providers.of("f")).in(CustomScoped.class); expose(f); } }); } @Provides @Named("E") @CustomScoped String provideE() { return "e"; } }; @SuppressWarnings("unchecked") // we know the module contains only bindings List moduleBindings = Elements.getElements(singletonBindings); ImmutableMap, Binding> map = indexBindings(moduleBindings); assertFalse(Scopes.isSingleton(map.get(a))); assertFalse(Scopes.isSingleton(map.get(b))); assertFalse(Scopes.isSingleton(map.get(c))); assertFalse(Scopes.isSingleton(map.get(d))); assertFalse(Scopes.isSingleton(map.get(e))); assertFalse(Scopes.isSingleton(map.get(f))); Injector injector = Guice.createInjector(singletonBindings); assertFalse(Scopes.isSingleton(injector.getBinding(a))); assertFalse(Scopes.isSingleton(injector.getBinding(b))); assertFalse(Scopes.isSingleton(injector.getBinding(c))); assertFalse(Scopes.isSingleton(injector.getBinding(d))); assertFalse(Scopes.isSingleton(injector.getBinding(e))); assertFalse(Scopes.isSingleton(injector.getBinding(f))); } public void testIsScopedPositive() { final Key a = Key.get(String.class, named("A")); final Key b = Key.get(String.class, named("B")); final Key c = Key.get(String.class, named("C")); final Key d = Key.get(String.class, named("D")); final Key e = Key.get(String.class, named("E")); final Key f = Key.get(Object.class, named("F")); final Key g = Key.get(String.class, named("G")); Module customBindings = new AbstractModule() { @Override protected void configure() { bindScope(CustomScoped.class, CUSTOM_SCOPE); bind(a).to(b); bind(b).to(c); bind(c).toProvider(Providers.of("c")).in(CUSTOM_SCOPE); bind(d).toProvider(Providers.of("d")).in(CustomScoped.class); bind(f).to(AnnotatedCustomScoped.class); install(new PrivateModule() { @Override protected void configure() { bind(g).toProvider(Providers.of("g")).in(CustomScoped.class); expose(g); } }); } @Provides @Named("E") @CustomScoped String provideE() { return "e"; } }; @SuppressWarnings("unchecked") // we know the module contains only bindings List moduleBindings = Elements.getElements(customBindings); ImmutableMap, Binding> map = indexBindings(moduleBindings); assertFalse(isCustomScoped(map.get(a))); // linked bindings are not followed by modules assertFalse(isCustomScoped(map.get(b))); assertTrue(isCustomScoped(map.get(c))); assertTrue(isCustomScoped(map.get(d))); assertTrue(isCustomScoped(map.get(e))); assertFalse(isCustomScoped(map.get(f))); // annotated classes are not followed by modules assertTrue(isCustomScoped(map.get(g))); Injector injector = Guice.createInjector(customBindings); assertTrue(isCustomScoped(injector.getBinding(a))); assertTrue(isCustomScoped(injector.getBinding(b))); assertTrue(isCustomScoped(injector.getBinding(c))); assertTrue(isCustomScoped(injector.getBinding(d))); assertTrue(isCustomScoped(injector.getBinding(e))); assertTrue(isCustomScoped(injector.getBinding(f))); assertTrue(isCustomScoped(injector.getBinding(g))); } public void testIsScopedNegative() { final Key a = Key.get(String.class, named("A")); final Key b = Key.get(String.class, named("B")); final Key c = Key.get(String.class, named("C")); final Key d = Key.get(String.class, named("D")); final Key e = Key.get(String.class, named("E")); final Key f = Key.get(String.class, named("F")); final Key g = Key.get(String.class, named("G")); final Key h = Key.get(String.class, named("H")); Module customBindings = new AbstractModule() { @Override protected void configure() { bind(a).to(b); bind(b).to(c); bind(c).toProvider(Providers.of("c")).in(Scopes.NO_SCOPE); bind(d).toProvider(Providers.of("d")).in(Singleton.class); install(new PrivateModule() { @Override protected void configure() { bind(f).toProvider(Providers.of("f")).in(Singleton.class); expose(f); } }); bind(g).toInstance("g"); bind(h).toProvider(Providers.of("h")).asEagerSingleton(); } @Provides @Named("E") @Singleton String provideE() { return "e"; } }; @SuppressWarnings("unchecked") // we know the module contains only bindings List moduleBindings = Elements.getElements(customBindings); ImmutableMap, Binding> map = indexBindings(moduleBindings); assertFalse(isCustomScoped(map.get(a))); assertFalse(isCustomScoped(map.get(b))); assertFalse(isCustomScoped(map.get(c))); assertFalse(isCustomScoped(map.get(d))); assertFalse(isCustomScoped(map.get(e))); assertFalse(isCustomScoped(map.get(f))); assertFalse(isCustomScoped(map.get(g))); assertFalse(isCustomScoped(map.get(h))); Injector injector = Guice.createInjector(customBindings); assertFalse(isCustomScoped(injector.getBinding(a))); assertFalse(isCustomScoped(injector.getBinding(b))); assertFalse(isCustomScoped(injector.getBinding(c))); assertFalse(isCustomScoped(injector.getBinding(d))); assertFalse(isCustomScoped(injector.getBinding(e))); assertFalse(isCustomScoped(injector.getBinding(f))); assertFalse(isCustomScoped(injector.getBinding(g))); assertFalse(isCustomScoped(injector.getBinding(h))); } private boolean isCustomScoped(Binding binding) { return Scopes.isScoped(binding, CUSTOM_SCOPE, CustomScoped.class); } ImmutableMap, Binding> indexBindings(Iterable elements) { ImmutableMap.Builder, Binding> builder = ImmutableMap.builder(); for (Element element : elements) { if (element instanceof Binding) { Binding binding = (Binding) element; builder.put(binding.getKey(), binding); } else if (element instanceof PrivateElements) { PrivateElements privateElements = (PrivateElements)element; Map, Binding> privateBindings = indexBindings(privateElements.getElements()); for(Key exposed : privateElements.getExposedKeys()) { builder.put(exposed, privateBindings.get(exposed)); } } } return builder.build(); } @Singleton static class ThrowingSingleton { static int nextInstanceId; final int instanceId = nextInstanceId++; ThrowingSingleton() { if (instanceId == 0) { throw new RuntimeException(); } } } public void testSingletonConstructorThrows() { Injector injector = Guice.createInjector(); try { injector.getInstance(ThrowingSingleton.class); fail(); } catch (ProvisionException expected) { } // this behaviour is unspecified. If we change Guice to re-throw the exception, this test // should be changed injector.getInstance(ThrowingSingleton.class); assertEquals(2, ThrowingSingleton.nextInstanceId); } /** * Should only be created by {@link SBarrierProvider}. * *

{@code S} stands for synchronization. * * @see SBarrierProvider */ static class S { private S(int preventInjectionWithoutProvider) { } } /** * Provides all the instances of S simultaneously using {@link CyclicBarrier} with {@code * nThreads}. Intended to be used for threads synchronization during injection. */ static class SBarrierProvider implements Provider { final CyclicBarrier barrier; volatile boolean barrierPassed = false; SBarrierProvider(int nThreads) { barrier = new CyclicBarrier(nThreads, new Runnable() { public void run() { // would finish before returning from await() for any thread barrierPassed = true; } }); } public S get() { try { if (!barrierPassed) { // only if we're triggering barrier for the first time barrier.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); } } catch (Exception e) { throw new RuntimeException(e); } return new S(0); } } /** * Tests that different injectors should not affect each other. * *

This creates a second thread to work in parallel, to create two instances of * {@link S} as the same time. If the lock if not granular enough (i.e. JVM-wide) * then they would block each other creating a deadlock and await timeout. */ public void testInjectorsDontDeadlockOnSingletons() throws Exception { final Provider provider = new SBarrierProvider(2); final Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { Thread.currentThread().setName("S.class[1]"); bind(S.class).toProvider(provider).in(Scopes.SINGLETON); } }); final Injector secondInjector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { Thread.currentThread().setName("S.class[2]"); bind(S.class).toProvider(provider).in(Scopes.SINGLETON); } }); Future secondThreadResult = Executors.newSingleThreadExecutor().submit(new Callable() { public S call() { return secondInjector.getInstance(S.class); } }); S firstS = injector.getInstance(S.class); S secondS = secondThreadResult.get(); assertNotSame(firstS, secondS); } @ImplementedBy(GImpl.class) interface G { } @Singleton static class GImpl implements G { final H h; /** * Relies on Guice implementation to inject S first and H later, which provides a barrier . */ @Inject GImpl(S synchronizationBarrier, H h) { this.h = h; } } @ImplementedBy(HImpl.class) interface H { } @Singleton static class HImpl implements H { final G g; /** * Relies on Guice implementation to inject S first and G later, which provides a barrier . */ @Inject HImpl(S synchronizationBarrier, G g) throws Exception { this.g = g; } } /** * Tests that injector can create two singletons with circular dependency in parallel. * *

This creates two threads to work in parallel, to create instances of * {@link G} and {@link H}. Creation is synchronized by injection of {@link S}, * first thread would block until second would be inside a singleton creation as well. * *

Both instances are created by sibling injectors, that share singleton scope. * Verifies that exactly one circular proxy object is created. */ public void testSiblingInjectorGettingCircularSingletonsOneCircularProxy() throws Exception { final Provider provider = new SBarrierProvider(2); final Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bind(S.class).toProvider(provider); } }); Future firstThreadResult = Executors.newSingleThreadExecutor().submit(new Callable() { public G call() { Thread.currentThread().setName("G.class"); return injector.createChildInjector().getInstance(G.class); } }); Future secondThreadResult = Executors.newSingleThreadExecutor().submit(new Callable() { public H call() { Thread.currentThread().setName("H.class"); return injector.createChildInjector().getInstance(H.class); } }); // using separate threads to avoid potential deadlock on the main thread // waiting twice as much to be sure that both would time out in their respective barriers GImpl g = (GImpl) firstThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); HImpl h = (HImpl) secondThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); // Check that G and H created are not proxied assertTrue(!Scopes.isCircularProxy(g) && !Scopes.isCircularProxy(h)); // Check that we have no more than one circular proxy created assertFalse(Scopes.isCircularProxy(g.h) && Scopes.isCircularProxy(h.g)); // Check that non-proxy variable points to another singleton assertTrue(g.h == h || h.g == g); // Check correct proxy initialization as default equals implementation would assertEquals(g.h, h); assertEquals(h.g, g); } @Singleton static class I0 { /** * Relies on Guice implementation to inject S first, which provides a barrier . */ @Inject I0(I1 i) { } } @Singleton static class I1 { /** * Relies on Guice implementation to inject S first, which provides a barrier . */ @Inject I1(S synchronizationBarrier, I2 i) { } } @Singleton static class I2 { /** * Relies on Guice implementation to inject S first, which provides a barrier . */ @Inject I2(J1 j) { } } @Singleton static class J0 { /** * Relies on Guice implementation to inject S first, which provides a barrier . */ @Inject J0(J1 j) { } } @Singleton static class J1 { /** * Relies on Guice implementation to inject S first, which provides a barrier . */ @Inject J1(S synchronizationBarrier, J2 j) { } } @Singleton static class J2 { /** * Relies on Guice implementation to inject S first, which provides a barrier . */ @Inject J2(K1 k) { } } @Singleton static class K0 { /** * Relies on Guice implementation to inject S first, which provides a barrier . */ @Inject K0(K1 k) { } } @Singleton static class K1 { /** * Relies on Guice implementation to inject S first, which provides a barrier . */ @Inject K1(S synchronizationBarrier, K2 k) { } } @Singleton static class K2 { /** * Relies on Guice implementation to inject S first, which provides a barrier . */ @Inject K2(I1 i) { } } /** * Check that circular dependencies on non-interfaces are correctly resolved in multi-threaded * case. And that an error message constructed is a good one. * *

I0 -> I1 -> I2 -> J1 and J0 -> J1 -> J2 -> K1 and K0 -> K1 -> K2, * where I1, J1 and K1 are created in parallel. * *

Creation is synchronized by injection of {@link S}, first thread would block until second * would be inside a singleton creation as well. * *

Verifies that provision results in an error, that spans two threads and * has a dependency cycle. */ public void testUnresolvableSingletonCircularDependencyErrorMessage() throws Exception { final Provider provider = new SBarrierProvider(3); final Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { bind(S.class).toProvider(provider); } }); Future firstThreadResult = Executors.newSingleThreadExecutor().submit(new Callable() { public I0 call() { Thread.currentThread().setName("I0.class"); return injector.getInstance(I0.class); } }); Future secondThreadResult = Executors.newSingleThreadExecutor().submit(new Callable() { public J0 call() { Thread.currentThread().setName("J0.class"); return injector.getInstance(J0.class); } }); Future thirdThreadResult = Executors.newSingleThreadExecutor().submit(new Callable() { public K0 call() { Thread.currentThread().setName("K0.class"); return injector.getInstance(K0.class); } }); // using separate threads to avoid potential deadlock on the main thread // waiting twice as much to be sure that both would time out in their respective barriers Throwable firstException = null; Throwable secondException = null; Throwable thirdException = null; try { firstThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); fail(); } catch (ExecutionException e) { firstException = e.getCause(); } try { secondThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); fail(); } catch (ExecutionException e) { secondException = e.getCause(); } try { thirdThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); fail(); } catch (ExecutionException e) { thirdException = e.getCause(); } // verification of error messages generated assertEquals(firstException.getClass(), ProvisionException.class); assertEquals(secondException.getClass(), ProvisionException.class); assertEquals(thirdException.getClass(), ProvisionException.class); List errorMessages = Lists.newArrayList( String.format("%s\n%s\n%s", firstException.getMessage(), secondException.getMessage(), thirdException.getMessage()) .split("\\n\\n")); Collections.sort(errorMessages, new Comparator() { @Override public int compare(String s1, String s2) { return s2.length() - s1.length(); } }); // this is brittle, but turns out that second to longest message spans all threads String errorMessage = errorMessages.get(1); assertContains(errorMessage, "Encountered circular dependency spanning several threads. Tried proxying " + this.getClass().getName()); assertFalse("Both I0 and J0 can not be a part of a dependency cycle", errorMessage.contains(I0.class.getName()) && errorMessage.contains(J0.class.getName())); assertFalse("Both J0 and K0 can not be a part of a dependency cycle", errorMessage.contains(J0.class.getName()) && errorMessage.contains(K0.class.getName())); assertFalse("Both K0 and I0 can not be a part of a dependency cycle", errorMessage.contains(K0.class.getName()) && errorMessage.contains(I0.class.getName())); List firstErrorLineForThread = new ArrayList(); boolean addNextLine = false; for (String errorLine : errorMessage.split("\\n")) { if (errorLine.contains("in thread")) { addNextLine = true; firstErrorLineForThread.add(errorLine); } else if (addNextLine) { addNextLine = false; firstErrorLineForThread.add(errorLine); } } assertEquals("we expect to see [T1, $A, T2, $B, T3, $C, T1, $A]", 8, firstErrorLineForThread.size()); assertEquals("first four elements should be different", 6, new HashSet(firstErrorLineForThread.subList(0, 6)).size()); assertEquals(firstErrorLineForThread.get(6), firstErrorLineForThread.get(0)); assertEquals(firstErrorLineForThread.get(7), firstErrorLineForThread.get(1)); assertFalse("K0 thread could not be blocked by J0", firstErrorLineForThread.get(0).contains("J0") && firstErrorLineForThread.get(2).contains("K0")); assertFalse("J0 thread could not be blocked by I0", firstErrorLineForThread.get(0).contains("I0") && firstErrorLineForThread.get(2).contains("J0")); assertFalse("I0 thread could not be blocked by K0", firstErrorLineForThread.get(0).contains("K0") && firstErrorLineForThread.get(2).contains("I0")); } }