| /** |
| * 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<E> { |
| 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<String> 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<Key<?>, Provider<?>> providers = Maps.newHashMap(); |
| public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) { |
| providers.put(key, unscoped); |
| return unscoped; |
| } |
| } |
| |
| public void testSingletonAnnotationOnParameterizedType() { |
| Injector injector = Guice.createInjector(); |
| assertSame(injector.getInstance(new Key<Injected<String>>() {}), |
| injector.getInstance(new Key<Injected<String>>() {})); |
| assertSame(injector.getInstance(new Key<In<Integer>>() {}), |
| injector.getInstance(new Key<In<Short>>() {})); |
| } |
| |
| @ImplementedBy(Injected.class) public interface In<T> {} |
| @Singleton public static class Injected<T> implements In<T> {} |
| |
| @Target({ ElementType.TYPE, ElementType.METHOD }) |
| @Retention(RUNTIME) |
| @ScopeAnnotation |
| public @interface CustomScoped {} |
| |
| static final Scope CUSTOM_SCOPE = new Scope() { |
| public <T> Provider<T> scope(Key<T> key, Provider<T> 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<ProvidedBySingleton> { |
| 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<B> bProvider; |
| |
| public <T> Provider<T> scope(Key<T> key, final Provider<T> unscoped) { |
| return new Provider<T>() { |
| public T get() { |
| bProvider.get(); |
| return unscoped.get(); |
| } |
| }; |
| } |
| } |
| |
| public void testIsSingletonPositive() { |
| final Key<String> a = Key.get(String.class, named("A")); |
| final Key<String> b = Key.get(String.class, named("B")); |
| final Key<String> c = Key.get(String.class, named("C")); |
| final Key<String> d = Key.get(String.class, named("D")); |
| final Key<String> e = Key.get(String.class, named("E")); |
| final Key<String> f = Key.get(String.class, named("F")); |
| final Key<String> g = Key.get(String.class, named("G")); |
| final Key<Object> h = Key.get(Object.class, named("H")); |
| final Key<String> 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<Element> moduleBindings = Elements.getElements(singletonBindings); |
| ImmutableMap<Key<?>, 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<String> a = Key.get(String.class, named("A")); |
| final Key<String> b = Key.get(String.class, named("B")); |
| final Key<String> c = Key.get(String.class, named("C")); |
| final Key<String> d = Key.get(String.class, named("D")); |
| final Key<String> e = Key.get(String.class, named("E")); |
| final Key<String> 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<Element> moduleBindings = Elements.getElements(singletonBindings); |
| ImmutableMap<Key<?>, 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<String> a = Key.get(String.class, named("A")); |
| final Key<String> b = Key.get(String.class, named("B")); |
| final Key<String> c = Key.get(String.class, named("C")); |
| final Key<String> d = Key.get(String.class, named("D")); |
| final Key<String> e = Key.get(String.class, named("E")); |
| final Key<Object> f = Key.get(Object.class, named("F")); |
| final Key<String> 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<Element> moduleBindings = Elements.getElements(customBindings); |
| ImmutableMap<Key<?>, 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<String> a = Key.get(String.class, named("A")); |
| final Key<String> b = Key.get(String.class, named("B")); |
| final Key<String> c = Key.get(String.class, named("C")); |
| final Key<String> d = Key.get(String.class, named("D")); |
| final Key<String> e = Key.get(String.class, named("E")); |
| final Key<String> f = Key.get(String.class, named("F")); |
| final Key<String> g = Key.get(String.class, named("G")); |
| final Key<String> 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<Element> moduleBindings = Elements.getElements(customBindings); |
| ImmutableMap<Key<?>, 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<Key<?>, Binding<?>> indexBindings(Iterable<Element> elements) { |
| ImmutableMap.Builder<Key<?>, 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<Key<?>, 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}. |
| * |
| * <p>{@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<S> { |
| |
| 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. |
| * |
| * <p>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<S> 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<S> secondThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<S>() { |
| 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. |
| * |
| * <p>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. |
| * |
| * <p>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<S> provider = new SBarrierProvider(2); |
| final Injector injector = Guice.createInjector(new AbstractModule() { |
| @Override |
| protected void configure() { |
| bind(S.class).toProvider(provider); |
| } |
| }); |
| |
| Future<G> firstThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<G>() { |
| public G call() { |
| Thread.currentThread().setName("G.class"); |
| return injector.createChildInjector().getInstance(G.class); |
| } |
| }); |
| Future<H> secondThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<H>() { |
| 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. |
| * |
| * <p>I0 -> I1 -> I2 -> J1 and J0 -> J1 -> J2 -> K1 and K0 -> K1 -> K2, |
| * where I1, J1 and K1 are created in parallel. |
| * |
| * <p>Creation is synchronized by injection of {@link S}, first thread would block until second |
| * would be inside a singleton creation as well. |
| * |
| * <p>Verifies that provision results in an error, that spans two threads and |
| * has a dependency cycle. |
| */ |
| |
| public void testUnresolvableSingletonCircularDependencyErrorMessage() throws Exception { |
| final Provider<S> provider = new SBarrierProvider(3); |
| final Injector injector = Guice.createInjector(new AbstractModule() { |
| @Override |
| protected void configure() { |
| bind(S.class).toProvider(provider); |
| } |
| }); |
| |
| Future<I0> firstThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<I0>() { |
| public I0 call() { |
| Thread.currentThread().setName("I0.class"); |
| return injector.getInstance(I0.class); |
| } |
| }); |
| Future<J0> secondThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<J0>() { |
| public J0 call() { |
| Thread.currentThread().setName("J0.class"); |
| return injector.getInstance(J0.class); |
| } |
| }); |
| Future<K0> thirdThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<K0>() { |
| 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<String> errorMessages = Lists.newArrayList( |
| String.format("%s\n%s\n%s", |
| firstException.getMessage(), secondException.getMessage(), thirdException.getMessage()) |
| .split("\\n\\n")); |
| Collections.sort(errorMessages, new Comparator<String>() { |
| @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<String> firstErrorLineForThread = new ArrayList<String>(); |
| 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<String>(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")); |
| } |
| } |