1 /** 2 * Copyright (C) 2006 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.inject; 18 19 import static com.google.inject.Asserts.asModuleChain; 20 import static com.google.inject.Asserts.assertContains; 21 import static com.google.inject.Asserts.getDeclaringSourcePart; 22 import static com.google.inject.name.Names.named; 23 import static java.lang.annotation.RetentionPolicy.RUNTIME; 24 25 import com.google.common.collect.ImmutableMap; 26 import com.google.common.collect.Lists; 27 import com.google.common.collect.Maps; 28 import com.google.inject.name.Named; 29 import com.google.inject.spi.Element; 30 import com.google.inject.spi.Elements; 31 import com.google.inject.spi.PrivateElements; 32 import com.google.inject.util.Providers; 33 34 import junit.framework.TestCase; 35 36 import java.io.IOException; 37 import java.lang.annotation.ElementType; 38 import java.lang.annotation.Retention; 39 import java.lang.annotation.Target; 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.Collections; 43 import java.util.Comparator; 44 import java.util.HashSet; 45 import java.util.Iterator; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.concurrent.Callable; 49 import java.util.concurrent.CyclicBarrier; 50 import java.util.concurrent.ExecutionException; 51 import java.util.concurrent.Executors; 52 import java.util.concurrent.Future; 53 import java.util.concurrent.TimeUnit; 54 55 /** 56 * @author crazybob@google.com (Bob Lee) 57 */ 58 public class ScopesTest extends TestCase { 59 60 static final long DEADLOCK_TIMEOUT_SECONDS = 1; 61 62 private final AbstractModule singletonsModule = new AbstractModule() { 63 @Override protected void configure() { 64 bind(BoundAsSingleton.class).in(Scopes.SINGLETON); 65 bind(AnnotatedSingleton.class); 66 bind(EagerSingleton.class).asEagerSingleton(); 67 bind(LinkedSingleton.class).to(RealLinkedSingleton.class); 68 bind(DependsOnJustInTimeSingleton.class); 69 bind(NotASingleton.class); 70 bind(ImplementedBySingleton.class).in(Scopes.SINGLETON); 71 bind(ProvidedBySingleton.class).in(Scopes.SINGLETON); 72 } 73 }; 74 setUp()75 @Override protected void setUp() throws Exception { 76 AnnotatedSingleton.nextInstanceId = 0; 77 BoundAsSingleton.nextInstanceId = 0; 78 EagerSingleton.nextInstanceId = 0; 79 RealLinkedSingleton.nextInstanceId = 0; 80 JustInTimeSingleton.nextInstanceId = 0; 81 NotASingleton.nextInstanceId = 0; 82 Implementation.nextInstanceId = 0; 83 ProvidedBySingleton.nextInstanceId = 0; 84 ThrowingSingleton.nextInstanceId = 0; 85 } 86 testSingletons()87 public void testSingletons() { 88 Injector injector = Guice.createInjector(singletonsModule); 89 90 assertSame( 91 injector.getInstance(BoundAsSingleton.class), 92 injector.getInstance(BoundAsSingleton.class)); 93 94 assertSame( 95 injector.getInstance(AnnotatedSingleton.class), 96 injector.getInstance(AnnotatedSingleton.class)); 97 98 assertSame( 99 injector.getInstance(EagerSingleton.class), 100 injector.getInstance(EagerSingleton.class)); 101 102 assertSame( 103 injector.getInstance(LinkedSingleton.class), 104 injector.getInstance(LinkedSingleton.class)); 105 106 assertSame( 107 injector.getInstance(JustInTimeSingleton.class), 108 injector.getInstance(JustInTimeSingleton.class)); 109 110 assertNotSame( 111 injector.getInstance(NotASingleton.class), 112 injector.getInstance(NotASingleton.class)); 113 114 assertSame( 115 injector.getInstance(ImplementedBySingleton.class), 116 injector.getInstance(ImplementedBySingleton.class)); 117 118 assertSame( 119 injector.getInstance(ProvidedBySingleton.class), 120 injector.getInstance(ProvidedBySingleton.class)); 121 } 122 testJustInTimeAnnotatedSingleton()123 public void testJustInTimeAnnotatedSingleton() { 124 Injector injector = Guice.createInjector(); 125 126 assertSame( 127 injector.getInstance(AnnotatedSingleton.class), 128 injector.getInstance(AnnotatedSingleton.class)); 129 } 130 testSingletonIsPerInjector()131 public void testSingletonIsPerInjector() { 132 assertNotSame( 133 Guice.createInjector().getInstance(AnnotatedSingleton.class), 134 Guice.createInjector().getInstance(AnnotatedSingleton.class)); 135 } 136 testOverriddingAnnotation()137 public void testOverriddingAnnotation() { 138 Injector injector = Guice.createInjector(new AbstractModule() { 139 @Override protected void configure() { 140 bind(AnnotatedSingleton.class).in(Scopes.NO_SCOPE); 141 } 142 }); 143 144 assertNotSame( 145 injector.getInstance(AnnotatedSingleton.class), 146 injector.getInstance(AnnotatedSingleton.class)); 147 } 148 testScopingAnnotationsOnAbstractTypeViaBind()149 public void testScopingAnnotationsOnAbstractTypeViaBind() { 150 try { 151 Guice.createInjector(new AbstractModule() { 152 @Override protected void configure() { 153 bind(A.class).to(AImpl.class); 154 } 155 }); 156 fail(); 157 } catch (CreationException expected) { 158 assertContains(expected.getMessage(), 159 A.class.getName() + " is annotated with " + Singleton.class.getName(), 160 "but scope annotations are not supported for abstract types.", 161 "at " + A.class.getName() + ".class(ScopesTest.java:"); 162 } 163 } 164 165 @Singleton 166 interface A {} 167 static class AImpl implements A {} 168 169 @Retention(RUNTIME) 170 @interface Component {} 171 172 @Component 173 @Singleton 174 interface ComponentAnnotationTest {} 175 static class ComponentAnnotationTestImpl implements ComponentAnnotationTest {} 176 testScopingAnnotationsOnAbstractTypeIsValidForComponent()177 public void testScopingAnnotationsOnAbstractTypeIsValidForComponent() { 178 Guice.createInjector(new AbstractModule() { 179 @Override protected void configure() { 180 bind(ComponentAnnotationTest.class).to(ComponentAnnotationTestImpl.class); 181 } 182 }); 183 } 184 testScopingAnnotationsOnAbstractTypeViaImplementedBy()185 public void testScopingAnnotationsOnAbstractTypeViaImplementedBy() { 186 try { 187 Guice.createInjector().getInstance(D.class); 188 fail(); 189 } catch (ConfigurationException expected) { 190 assertContains(expected.getMessage(), 191 D.class.getName() + " is annotated with " + Singleton.class.getName(), 192 "but scope annotations are not supported for abstract types.", 193 "at " + D.class.getName() + ".class(ScopesTest.java:"); 194 } 195 } 196 197 @Singleton @ImplementedBy(DImpl.class) 198 interface D {} 199 static class DImpl implements D {} 200 testScopingAnnotationsOnAbstractTypeViaProvidedBy()201 public void testScopingAnnotationsOnAbstractTypeViaProvidedBy() { 202 try { 203 Guice.createInjector().getInstance(E.class); 204 fail(); 205 } catch (ConfigurationException expected) { 206 assertContains(expected.getMessage(), 207 E.class.getName() + " is annotated with " + Singleton.class.getName(), 208 "but scope annotations are not supported for abstract types.", 209 "at " + E.class.getName() + ".class(ScopesTest.java:"); 210 } 211 } 212 213 @Singleton @ProvidedBy(EProvider.class) 214 interface E {} 215 static class EProvider implements Provider<E> { get()216 public E get() { 217 return null; 218 } 219 } 220 testScopeUsedButNotBound()221 public void testScopeUsedButNotBound() { 222 try { 223 Guice.createInjector(new AbstractModule() { 224 @Override protected void configure() { 225 bind(B.class).in(CustomScoped.class); 226 bind(C.class); 227 } 228 }); 229 fail(); 230 } catch (CreationException expected) { 231 assertContains(expected.getMessage(), 232 "1) No scope is bound to " + CustomScoped.class.getName(), 233 "at " + getClass().getName(), getDeclaringSourcePart(getClass()), 234 "2) No scope is bound to " + CustomScoped.class.getName(), 235 "at " + C.class.getName() + ".class"); 236 } 237 } 238 239 static class B {} 240 241 @CustomScoped 242 static class C {} 243 testSingletonsInProductionStage()244 public void testSingletonsInProductionStage() { 245 Guice.createInjector(Stage.PRODUCTION, singletonsModule); 246 247 assertEquals(1, AnnotatedSingleton.nextInstanceId); 248 assertEquals(1, BoundAsSingleton.nextInstanceId); 249 assertEquals(1, EagerSingleton.nextInstanceId); 250 assertEquals(1, RealLinkedSingleton.nextInstanceId); 251 assertEquals(1, JustInTimeSingleton.nextInstanceId); 252 assertEquals(0, NotASingleton.nextInstanceId); 253 } 254 testSingletonsInDevelopmentStage()255 public void testSingletonsInDevelopmentStage() { 256 Guice.createInjector(Stage.DEVELOPMENT, singletonsModule); 257 258 assertEquals(0, AnnotatedSingleton.nextInstanceId); 259 assertEquals(0, BoundAsSingleton.nextInstanceId); 260 assertEquals(1, EagerSingleton.nextInstanceId); 261 assertEquals(0, RealLinkedSingleton.nextInstanceId); 262 assertEquals(0, JustInTimeSingleton.nextInstanceId); 263 assertEquals(0, NotASingleton.nextInstanceId); 264 } 265 testSingletonScopeIsNotSerializable()266 public void testSingletonScopeIsNotSerializable() throws IOException { 267 Asserts.assertNotSerializable(Scopes.SINGLETON); 268 } 269 testNoScopeIsNotSerializable()270 public void testNoScopeIsNotSerializable() throws IOException { 271 Asserts.assertNotSerializable(Scopes.NO_SCOPE); 272 } 273 testUnscopedProviderWorksOutsideOfRequestedScope()274 public void testUnscopedProviderWorksOutsideOfRequestedScope() { 275 final RememberProviderScope scope = new RememberProviderScope(); 276 277 Injector injector = Guice.createInjector(new AbstractModule() { 278 @Override protected void configure() { 279 bindScope(CustomScoped.class, scope); 280 bind(List.class).to(ArrayList.class).in(CustomScoped.class); 281 } 282 }); 283 284 injector.getInstance(List.class); 285 Provider<?> listProvider = scope.providers.get(Key.get(List.class)); 286 287 // this line fails with a NullPointerException because the Providers 288 // passed to Scope.scope() don't work outside of the scope() method. 289 assertTrue(listProvider.get() instanceof ArrayList); 290 } 291 292 static class OuterRuntimeModule extends AbstractModule { configure()293 @Override protected void configure() { 294 install(new InnerRuntimeModule()); 295 } 296 } 297 static class InnerRuntimeModule extends AbstractModule { configure()298 @Override protected void configure() { 299 bindScope(NotRuntimeRetainedScoped.class, Scopes.NO_SCOPE); 300 } 301 } testScopeAnnotationWithoutRuntimeRetention()302 public void testScopeAnnotationWithoutRuntimeRetention() { 303 try { 304 Guice.createInjector(new OuterRuntimeModule()); 305 fail(); 306 } catch (CreationException expected) { 307 assertContains(expected.getMessage(), 308 "1) Please annotate " + NotRuntimeRetainedScoped.class.getName() 309 + " with @Retention(RUNTIME).", 310 "at " + InnerRuntimeModule.class.getName() + getDeclaringSourcePart(getClass()), 311 asModuleChain(OuterRuntimeModule.class, InnerRuntimeModule.class)); 312 } 313 } 314 315 static class OuterDeprecatedModule extends AbstractModule { configure()316 @Override protected void configure() { 317 install(new InnerDeprecatedModule()); 318 } 319 } 320 static class InnerDeprecatedModule extends AbstractModule { configure()321 @Override protected void configure() { 322 bindScope(Deprecated.class, Scopes.NO_SCOPE); 323 } 324 } testBindScopeToAnnotationWithoutScopeAnnotation()325 public void testBindScopeToAnnotationWithoutScopeAnnotation() { 326 try { 327 Guice.createInjector(new OuterDeprecatedModule()); 328 fail(); 329 } catch (CreationException expected) { 330 assertContains(expected.getMessage(), 331 "1) Please annotate " + Deprecated.class.getName() + " with @ScopeAnnotation.", 332 "at " + InnerDeprecatedModule.class.getName() + getDeclaringSourcePart(getClass()), 333 asModuleChain(OuterDeprecatedModule.class, InnerDeprecatedModule.class)); 334 } 335 } 336 337 static class OuterScopeModule extends AbstractModule { configure()338 @Override protected void configure() { 339 install(new CustomNoScopeModule()); 340 install(new CustomSingletonModule()); 341 } 342 } 343 static class CustomNoScopeModule extends AbstractModule { configure()344 @Override protected void configure() { 345 bindScope(CustomScoped.class, Scopes.NO_SCOPE); 346 } 347 } 348 static class CustomSingletonModule extends AbstractModule { configure()349 @Override protected void configure() { 350 bindScope(CustomScoped.class, Scopes.SINGLETON); 351 } 352 } 353 testBindScopeTooManyTimes()354 public void testBindScopeTooManyTimes() { 355 try { 356 Guice.createInjector(new OuterScopeModule()); 357 fail(); 358 } catch (CreationException expected) { 359 assertContains(expected.getMessage(), 360 "1) Scope Scopes.NO_SCOPE is already bound to " + CustomScoped.class.getName() 361 + " at " + CustomNoScopeModule.class.getName() + getDeclaringSourcePart(getClass()), 362 asModuleChain(OuterScopeModule.class, CustomNoScopeModule.class), 363 "Cannot bind Scopes.SINGLETON.", 364 "at " + ScopesTest.class.getName(), getDeclaringSourcePart(getClass()), 365 asModuleChain(OuterScopeModule.class, CustomSingletonModule.class)); 366 } 367 } 368 testBindDuplicateScope()369 public void testBindDuplicateScope() { 370 Injector injector = Guice.createInjector(new AbstractModule() { 371 @Override protected void configure() { 372 bindScope(CustomScoped.class, Scopes.SINGLETON); 373 bindScope(CustomScoped.class, Scopes.SINGLETON); 374 } 375 }); 376 377 assertSame( 378 injector.getInstance(AnnotatedCustomScoped.class), 379 injector.getInstance(AnnotatedCustomScoped.class)); 380 } 381 testDuplicateScopeAnnotations()382 public void testDuplicateScopeAnnotations() { 383 Injector injector = Guice.createInjector(new AbstractModule() { 384 @Override protected void configure() { 385 bindScope(CustomScoped.class, Scopes.NO_SCOPE); 386 } 387 }); 388 389 try { 390 injector.getInstance(SingletonAndCustomScoped.class); 391 fail(); 392 } catch (ConfigurationException expected) { 393 assertContains(expected.getMessage(), 394 "1) More than one scope annotation was found: ", 395 "while locating " + SingletonAndCustomScoped.class.getName()); 396 } 397 } 398 testNullScopedAsASingleton()399 public void testNullScopedAsASingleton() { 400 Injector injector = Guice.createInjector(new AbstractModule() { 401 @Override 402 protected void configure() {} 403 404 final Iterator<String> values = Arrays.asList(null, "A").iterator(); 405 406 @Provides @Singleton String provideString() { 407 return values.next(); 408 } 409 }); 410 411 assertNull(injector.getInstance(String.class)); 412 assertNull(injector.getInstance(String.class)); 413 assertNull(injector.getInstance(String.class)); 414 } 415 416 class RememberProviderScope implements Scope { 417 final Map<Key<?>, Provider<?>> providers = Maps.newHashMap(); scope(Key<T> key, Provider<T> unscoped)418 public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) { 419 providers.put(key, unscoped); 420 return unscoped; 421 } 422 } 423 testSingletonAnnotationOnParameterizedType()424 public void testSingletonAnnotationOnParameterizedType() { 425 Injector injector = Guice.createInjector(); 426 assertSame(injector.getInstance(new Key<Injected<String>>() {}), 427 injector.getInstance(new Key<Injected<String>>() {})); 428 assertSame(injector.getInstance(new Key<In<Integer>>() {}), 429 injector.getInstance(new Key<In<Short>>() {})); 430 } 431 432 @ImplementedBy(Injected.class) public interface In<T> {} 433 @Singleton public static class Injected<T> implements In<T> {} 434 435 @Target({ ElementType.TYPE, ElementType.METHOD }) 436 @Retention(RUNTIME) 437 @ScopeAnnotation 438 public @interface CustomScoped {} 439 440 static final Scope CUSTOM_SCOPE = new Scope() { 441 public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) { 442 return Scopes.SINGLETON.scope(key, unscoped); 443 } 444 }; 445 446 @Target({ ElementType.TYPE, ElementType.METHOD }) 447 @ScopeAnnotation 448 public @interface NotRuntimeRetainedScoped {} 449 450 @CustomScoped 451 static class AnnotatedCustomScoped {} 452 453 @Singleton 454 static class AnnotatedSingleton { 455 static int nextInstanceId; 456 final int instanceId = nextInstanceId++; 457 } 458 459 static class BoundAsSingleton { 460 static int nextInstanceId; 461 final int instanceId = nextInstanceId++; 462 } 463 464 static class EagerSingleton { 465 static int nextInstanceId; 466 final int instanceId = nextInstanceId++; 467 } 468 469 interface LinkedSingleton {} 470 471 @Singleton 472 static class RealLinkedSingleton implements LinkedSingleton { 473 static int nextInstanceId; 474 final int instanceId = nextInstanceId++; 475 } 476 477 static class DependsOnJustInTimeSingleton { 478 @Inject JustInTimeSingleton justInTimeSingleton; 479 } 480 481 @Singleton 482 static class JustInTimeSingleton { 483 static int nextInstanceId; 484 final int instanceId = nextInstanceId++; 485 } 486 487 static class NotASingleton { 488 static int nextInstanceId; 489 final int instanceId = nextInstanceId++; 490 } 491 492 @SuppressWarnings("MoreThanOneScopeAnnotationOnClass") // suppress compiler error for testing 493 @Singleton 494 @CustomScoped 495 static class SingletonAndCustomScoped {} 496 497 @ImplementedBy(Implementation.class) 498 static interface ImplementedBySingleton {} 499 500 @ProvidedBy(ImplementationProvider.class) 501 static class ProvidedBySingleton { 502 static int nextInstanceId; 503 final int instanceId = nextInstanceId++; 504 } 505 506 static class Implementation implements ImplementedBySingleton { 507 static int nextInstanceId; 508 final int instanceId = nextInstanceId++; 509 } 510 511 static class ImplementationProvider implements Provider<ProvidedBySingleton> { get()512 public ProvidedBySingleton get() { 513 return new ProvidedBySingleton(); 514 } 515 } 516 testScopeThatGetsAnUnrelatedObject()517 public void testScopeThatGetsAnUnrelatedObject() { 518 Injector injector = Guice.createInjector(new AbstractModule() { 519 @Override protected void configure() { 520 bind(B.class); 521 bind(C.class); 522 ProviderGetScope providerGetScope = new ProviderGetScope(); 523 requestInjection(providerGetScope); 524 bindScope(CustomScoped.class, providerGetScope); 525 } 526 }); 527 528 injector.getInstance(C.class); 529 } 530 531 class ProviderGetScope implements Scope { 532 @Inject Provider<B> bProvider; 533 scope(Key<T> key, final Provider<T> unscoped)534 public <T> Provider<T> scope(Key<T> key, final Provider<T> unscoped) { 535 return new Provider<T>() { 536 public T get() { 537 bProvider.get(); 538 return unscoped.get(); 539 } 540 }; 541 } 542 } 543 544 public void testIsSingletonPositive() { 545 final Key<String> a = Key.get(String.class, named("A")); 546 final Key<String> b = Key.get(String.class, named("B")); 547 final Key<String> c = Key.get(String.class, named("C")); 548 final Key<String> d = Key.get(String.class, named("D")); 549 final Key<String> e = Key.get(String.class, named("E")); 550 final Key<String> f = Key.get(String.class, named("F")); 551 final Key<String> g = Key.get(String.class, named("G")); 552 final Key<Object> h = Key.get(Object.class, named("H")); 553 final Key<String> i = Key.get(String.class, named("I")); 554 555 Module singletonBindings = new AbstractModule() { 556 @Override protected void configure() { 557 bind(a).to(b); 558 bind(b).to(c); 559 bind(c).toProvider(Providers.of("c")).in(Scopes.SINGLETON); 560 bind(d).toInstance("d"); 561 bind(e).toProvider(Providers.of("e")).asEagerSingleton(); 562 bind(f).toProvider(Providers.of("f")).in(Singleton.class); 563 bind(h).to(AnnotatedSingleton.class); 564 install(new PrivateModule() { 565 @Override protected void configure() { 566 bind(i).toProvider(Providers.of("i")).in(Singleton.class); 567 expose(i); 568 } 569 }); 570 } 571 572 @Provides @Named("G") @Singleton String provideG() { 573 return "g"; 574 } 575 }; 576 577 @SuppressWarnings("unchecked") // we know the module contains only bindings 578 List<Element> moduleBindings = Elements.getElements(singletonBindings); 579 ImmutableMap<Key<?>, Binding<?>> map = indexBindings(moduleBindings); 580 assertFalse(Scopes.isSingleton(map.get(a))); // linked bindings are not followed by modules 581 assertFalse(Scopes.isSingleton(map.get(b))); 582 assertTrue(Scopes.isSingleton(map.get(c))); 583 assertTrue(Scopes.isSingleton(map.get(d))); 584 assertTrue(Scopes.isSingleton(map.get(e))); 585 assertTrue(Scopes.isSingleton(map.get(f))); 586 assertTrue(Scopes.isSingleton(map.get(g))); 587 assertFalse(Scopes.isSingleton(map.get(h))); // annotated classes are not followed by modules 588 assertTrue(Scopes.isSingleton(map.get(i))); 589 590 Injector injector = Guice.createInjector(singletonBindings); 591 assertTrue(Scopes.isSingleton(injector.getBinding(a))); 592 assertTrue(Scopes.isSingleton(injector.getBinding(b))); 593 assertTrue(Scopes.isSingleton(injector.getBinding(c))); 594 assertTrue(Scopes.isSingleton(injector.getBinding(d))); 595 assertTrue(Scopes.isSingleton(injector.getBinding(e))); 596 assertTrue(Scopes.isSingleton(injector.getBinding(f))); 597 assertTrue(Scopes.isSingleton(injector.getBinding(g))); 598 assertTrue(Scopes.isSingleton(injector.getBinding(h))); 599 assertTrue(Scopes.isSingleton(injector.getBinding(i))); 600 } 601 602 public void testIsSingletonNegative() { 603 final Key<String> a = Key.get(String.class, named("A")); 604 final Key<String> b = Key.get(String.class, named("B")); 605 final Key<String> c = Key.get(String.class, named("C")); 606 final Key<String> d = Key.get(String.class, named("D")); 607 final Key<String> e = Key.get(String.class, named("E")); 608 final Key<String> f = Key.get(String.class, named("F")); 609 610 Module singletonBindings = new AbstractModule() { 611 @Override protected void configure() { 612 bind(a).to(b); 613 bind(b).to(c); 614 bind(c).toProvider(Providers.of("c")).in(Scopes.NO_SCOPE); 615 bind(d).toProvider(Providers.of("d")).in(CustomScoped.class); 616 bindScope(CustomScoped.class, Scopes.NO_SCOPE); 617 install(new PrivateModule() { 618 @Override protected void configure() { 619 bind(f).toProvider(Providers.of("f")).in(CustomScoped.class); 620 expose(f); 621 } 622 }); 623 } 624 625 @Provides @Named("E") @CustomScoped String provideE() { 626 return "e"; 627 } 628 }; 629 630 @SuppressWarnings("unchecked") // we know the module contains only bindings 631 List<Element> moduleBindings = Elements.getElements(singletonBindings); 632 ImmutableMap<Key<?>, Binding<?>> map = indexBindings(moduleBindings); 633 assertFalse(Scopes.isSingleton(map.get(a))); 634 assertFalse(Scopes.isSingleton(map.get(b))); 635 assertFalse(Scopes.isSingleton(map.get(c))); 636 assertFalse(Scopes.isSingleton(map.get(d))); 637 assertFalse(Scopes.isSingleton(map.get(e))); 638 assertFalse(Scopes.isSingleton(map.get(f))); 639 640 Injector injector = Guice.createInjector(singletonBindings); 641 assertFalse(Scopes.isSingleton(injector.getBinding(a))); 642 assertFalse(Scopes.isSingleton(injector.getBinding(b))); 643 assertFalse(Scopes.isSingleton(injector.getBinding(c))); 644 assertFalse(Scopes.isSingleton(injector.getBinding(d))); 645 assertFalse(Scopes.isSingleton(injector.getBinding(e))); 646 assertFalse(Scopes.isSingleton(injector.getBinding(f))); 647 } 648 649 public void testIsScopedPositive() { 650 final Key<String> a = Key.get(String.class, named("A")); 651 final Key<String> b = Key.get(String.class, named("B")); 652 final Key<String> c = Key.get(String.class, named("C")); 653 final Key<String> d = Key.get(String.class, named("D")); 654 final Key<String> e = Key.get(String.class, named("E")); 655 final Key<Object> f = Key.get(Object.class, named("F")); 656 final Key<String> g = Key.get(String.class, named("G")); 657 658 Module customBindings = new AbstractModule() { 659 @Override protected void configure() { 660 bindScope(CustomScoped.class, CUSTOM_SCOPE); 661 bind(a).to(b); 662 bind(b).to(c); 663 bind(c).toProvider(Providers.of("c")).in(CUSTOM_SCOPE); 664 bind(d).toProvider(Providers.of("d")).in(CustomScoped.class); 665 bind(f).to(AnnotatedCustomScoped.class); 666 install(new PrivateModule() { 667 @Override protected void configure() { 668 bind(g).toProvider(Providers.of("g")).in(CustomScoped.class); 669 expose(g); 670 } 671 }); 672 } 673 674 @Provides @Named("E") @CustomScoped String provideE() { 675 return "e"; 676 } 677 }; 678 679 @SuppressWarnings("unchecked") // we know the module contains only bindings 680 List<Element> moduleBindings = Elements.getElements(customBindings); 681 ImmutableMap<Key<?>, Binding<?>> map = indexBindings(moduleBindings); 682 assertFalse(isCustomScoped(map.get(a))); // linked bindings are not followed by modules 683 assertFalse(isCustomScoped(map.get(b))); 684 assertTrue(isCustomScoped(map.get(c))); 685 assertTrue(isCustomScoped(map.get(d))); 686 assertTrue(isCustomScoped(map.get(e))); 687 assertFalse(isCustomScoped(map.get(f))); // annotated classes are not followed by modules 688 assertTrue(isCustomScoped(map.get(g))); 689 690 Injector injector = Guice.createInjector(customBindings); 691 assertTrue(isCustomScoped(injector.getBinding(a))); 692 assertTrue(isCustomScoped(injector.getBinding(b))); 693 assertTrue(isCustomScoped(injector.getBinding(c))); 694 assertTrue(isCustomScoped(injector.getBinding(d))); 695 assertTrue(isCustomScoped(injector.getBinding(e))); 696 assertTrue(isCustomScoped(injector.getBinding(f))); 697 assertTrue(isCustomScoped(injector.getBinding(g))); 698 } 699 700 public void testIsScopedNegative() { 701 final Key<String> a = Key.get(String.class, named("A")); 702 final Key<String> b = Key.get(String.class, named("B")); 703 final Key<String> c = Key.get(String.class, named("C")); 704 final Key<String> d = Key.get(String.class, named("D")); 705 final Key<String> e = Key.get(String.class, named("E")); 706 final Key<String> f = Key.get(String.class, named("F")); 707 final Key<String> g = Key.get(String.class, named("G")); 708 final Key<String> h = Key.get(String.class, named("H")); 709 710 Module customBindings = new AbstractModule() { 711 @Override protected void configure() { 712 bind(a).to(b); 713 bind(b).to(c); 714 bind(c).toProvider(Providers.of("c")).in(Scopes.NO_SCOPE); 715 bind(d).toProvider(Providers.of("d")).in(Singleton.class); 716 install(new PrivateModule() { 717 @Override 718 protected void configure() { 719 bind(f).toProvider(Providers.of("f")).in(Singleton.class); 720 expose(f); 721 } 722 }); 723 bind(g).toInstance("g"); 724 bind(h).toProvider(Providers.of("h")).asEagerSingleton(); 725 } 726 727 @Provides @Named("E") @Singleton String provideE() { 728 return "e"; 729 } 730 }; 731 732 @SuppressWarnings("unchecked") // we know the module contains only bindings 733 List<Element> moduleBindings = Elements.getElements(customBindings); 734 ImmutableMap<Key<?>, Binding<?>> map = indexBindings(moduleBindings); 735 assertFalse(isCustomScoped(map.get(a))); 736 assertFalse(isCustomScoped(map.get(b))); 737 assertFalse(isCustomScoped(map.get(c))); 738 assertFalse(isCustomScoped(map.get(d))); 739 assertFalse(isCustomScoped(map.get(e))); 740 assertFalse(isCustomScoped(map.get(f))); 741 assertFalse(isCustomScoped(map.get(g))); 742 assertFalse(isCustomScoped(map.get(h))); 743 744 Injector injector = Guice.createInjector(customBindings); 745 assertFalse(isCustomScoped(injector.getBinding(a))); 746 assertFalse(isCustomScoped(injector.getBinding(b))); 747 assertFalse(isCustomScoped(injector.getBinding(c))); 748 assertFalse(isCustomScoped(injector.getBinding(d))); 749 assertFalse(isCustomScoped(injector.getBinding(e))); 750 assertFalse(isCustomScoped(injector.getBinding(f))); 751 assertFalse(isCustomScoped(injector.getBinding(g))); 752 assertFalse(isCustomScoped(injector.getBinding(h))); 753 } 754 755 private boolean isCustomScoped(Binding<?> binding) { 756 return Scopes.isScoped(binding, CUSTOM_SCOPE, CustomScoped.class); 757 } 758 759 ImmutableMap<Key<?>, Binding<?>> indexBindings(Iterable<Element> elements) { 760 ImmutableMap.Builder<Key<?>, Binding<?>> builder = ImmutableMap.builder(); 761 for (Element element : elements) { 762 if (element instanceof Binding) { 763 Binding<?> binding = (Binding<?>) element; 764 builder.put(binding.getKey(), binding); 765 } else if (element instanceof PrivateElements) { 766 PrivateElements privateElements = (PrivateElements)element; 767 Map<Key<?>, Binding<?>> privateBindings = indexBindings(privateElements.getElements()); 768 for(Key<?> exposed : privateElements.getExposedKeys()) { 769 builder.put(exposed, privateBindings.get(exposed)); 770 } 771 } 772 } 773 return builder.build(); 774 } 775 776 @Singleton 777 static class ThrowingSingleton { 778 static int nextInstanceId; 779 final int instanceId = nextInstanceId++; 780 781 ThrowingSingleton() { 782 if (instanceId == 0) { 783 throw new RuntimeException(); 784 } 785 } 786 } 787 788 public void testSingletonConstructorThrows() { 789 Injector injector = Guice.createInjector(); 790 791 try { 792 injector.getInstance(ThrowingSingleton.class); 793 fail(); 794 } catch (ProvisionException expected) { 795 } 796 797 // this behaviour is unspecified. If we change Guice to re-throw the exception, this test 798 // should be changed 799 injector.getInstance(ThrowingSingleton.class); 800 assertEquals(2, ThrowingSingleton.nextInstanceId); 801 } 802 803 /** 804 * Should only be created by {@link SBarrierProvider}. 805 * 806 * <p>{@code S} stands for synchronization. 807 * 808 * @see SBarrierProvider 809 */ 810 static class S { 811 812 private S(int preventInjectionWithoutProvider) { 813 } 814 } 815 816 /** 817 * Provides all the instances of S simultaneously using {@link CyclicBarrier} with {@code 818 * nThreads}. Intended to be used for threads synchronization during injection. 819 */ 820 static class SBarrierProvider implements Provider<S> { 821 822 final CyclicBarrier barrier; 823 volatile boolean barrierPassed = false; 824 825 SBarrierProvider(int nThreads) { 826 barrier = new CyclicBarrier(nThreads, new Runnable() { 827 public void run() { 828 // would finish before returning from await() for any thread 829 barrierPassed = true; 830 } 831 }); 832 } 833 834 public S get() { 835 try { 836 if (!barrierPassed) { 837 // only if we're triggering barrier for the first time 838 barrier.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); 839 } 840 } catch (Exception e) { 841 throw new RuntimeException(e); 842 } 843 return new S(0); 844 } 845 } 846 847 /** 848 * Tests that different injectors should not affect each other. 849 * 850 * <p>This creates a second thread to work in parallel, to create two instances of 851 * {@link S} as the same time. If the lock if not granular enough (i.e. JVM-wide) 852 * then they would block each other creating a deadlock and await timeout. 853 */ 854 855 public void testInjectorsDontDeadlockOnSingletons() throws Exception { 856 final Provider<S> provider = new SBarrierProvider(2); 857 final Injector injector = Guice.createInjector(new AbstractModule() { 858 @Override 859 protected void configure() { 860 Thread.currentThread().setName("S.class[1]"); 861 bind(S.class).toProvider(provider).in(Scopes.SINGLETON); 862 } 863 }); 864 final Injector secondInjector = Guice.createInjector(new AbstractModule() { 865 @Override 866 protected void configure() { 867 Thread.currentThread().setName("S.class[2]"); 868 bind(S.class).toProvider(provider).in(Scopes.SINGLETON); 869 } 870 }); 871 872 Future<S> secondThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<S>() { 873 public S call() { 874 return secondInjector.getInstance(S.class); 875 } 876 }); 877 878 S firstS = injector.getInstance(S.class); 879 S secondS = secondThreadResult.get(); 880 881 assertNotSame(firstS, secondS); 882 } 883 884 @ImplementedBy(GImpl.class) 885 interface G { 886 887 } 888 889 @Singleton 890 static class GImpl implements G { 891 892 final H h; 893 894 /** 895 * Relies on Guice implementation to inject S first and H later, which provides a barrier . 896 */ 897 @Inject 898 GImpl(S synchronizationBarrier, H h) { 899 this.h = h; 900 } 901 } 902 903 @ImplementedBy(HImpl.class) 904 interface H { 905 906 } 907 908 @Singleton 909 static class HImpl implements H { 910 911 final G g; 912 913 /** 914 * Relies on Guice implementation to inject S first and G later, which provides a barrier . 915 */ 916 @Inject 917 HImpl(S synchronizationBarrier, G g) throws Exception { 918 this.g = g; 919 } 920 } 921 922 /** 923 * Tests that injector can create two singletons with circular dependency in parallel. 924 * 925 * <p>This creates two threads to work in parallel, to create instances of 926 * {@link G} and {@link H}. Creation is synchronized by injection of {@link S}, 927 * first thread would block until second would be inside a singleton creation as well. 928 * 929 * <p>Both instances are created by sibling injectors, that share singleton scope. 930 * Verifies that exactly one circular proxy object is created. 931 */ 932 933 public void testSiblingInjectorGettingCircularSingletonsOneCircularProxy() throws Exception { 934 final Provider<S> provider = new SBarrierProvider(2); 935 final Injector injector = Guice.createInjector(new AbstractModule() { 936 @Override 937 protected void configure() { 938 bind(S.class).toProvider(provider); 939 } 940 }); 941 942 Future<G> firstThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<G>() { 943 public G call() { 944 Thread.currentThread().setName("G.class"); 945 return injector.createChildInjector().getInstance(G.class); 946 } 947 }); 948 Future<H> secondThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<H>() { 949 public H call() { 950 Thread.currentThread().setName("H.class"); 951 return injector.createChildInjector().getInstance(H.class); 952 } 953 }); 954 955 // using separate threads to avoid potential deadlock on the main thread 956 // waiting twice as much to be sure that both would time out in their respective barriers 957 GImpl g = (GImpl) firstThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); 958 HImpl h = (HImpl) secondThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); 959 960 // Check that G and H created are not proxied 961 assertTrue(!Scopes.isCircularProxy(g) && !Scopes.isCircularProxy(h)); 962 963 // Check that we have no more than one circular proxy created 964 assertFalse(Scopes.isCircularProxy(g.h) && Scopes.isCircularProxy(h.g)); 965 966 // Check that non-proxy variable points to another singleton 967 assertTrue(g.h == h || h.g == g); 968 969 // Check correct proxy initialization as default equals implementation would 970 assertEquals(g.h, h); 971 assertEquals(h.g, g); 972 } 973 974 @Singleton 975 static class I0 { 976 977 /** 978 * Relies on Guice implementation to inject S first, which provides a barrier . 979 */ 980 @Inject 981 I0(I1 i) { 982 } 983 } 984 985 @Singleton 986 static class I1 { 987 988 /** 989 * Relies on Guice implementation to inject S first, which provides a barrier . 990 */ 991 @Inject 992 I1(S synchronizationBarrier, I2 i) { 993 } 994 } 995 996 @Singleton 997 static class I2 { 998 999 /** 1000 * Relies on Guice implementation to inject S first, which provides a barrier . 1001 */ 1002 @Inject 1003 I2(J1 j) { 1004 } 1005 } 1006 1007 @Singleton 1008 static class J0 { 1009 1010 /** 1011 * Relies on Guice implementation to inject S first, which provides a barrier . 1012 */ 1013 @Inject 1014 J0(J1 j) { 1015 } 1016 } 1017 1018 @Singleton 1019 static class J1 { 1020 1021 /** 1022 * Relies on Guice implementation to inject S first, which provides a barrier . 1023 */ 1024 @Inject 1025 J1(S synchronizationBarrier, J2 j) { 1026 } 1027 } 1028 1029 @Singleton 1030 static class J2 { 1031 1032 /** 1033 * Relies on Guice implementation to inject S first, which provides a barrier . 1034 */ 1035 @Inject 1036 J2(K1 k) { 1037 } 1038 } 1039 1040 @Singleton 1041 static class K0 { 1042 1043 /** 1044 * Relies on Guice implementation to inject S first, which provides a barrier . 1045 */ 1046 @Inject 1047 K0(K1 k) { 1048 } 1049 } 1050 1051 @Singleton 1052 static class K1 { 1053 1054 /** 1055 * Relies on Guice implementation to inject S first, which provides a barrier . 1056 */ 1057 @Inject 1058 K1(S synchronizationBarrier, K2 k) { 1059 } 1060 } 1061 1062 @Singleton 1063 static class K2 { 1064 1065 /** 1066 * Relies on Guice implementation to inject S first, which provides a barrier . 1067 */ 1068 @Inject 1069 K2(I1 i) { 1070 } 1071 } 1072 1073 /** 1074 * Check that circular dependencies on non-interfaces are correctly resolved in multi-threaded 1075 * case. And that an error message constructed is a good one. 1076 * 1077 * <p>I0 -> I1 -> I2 -> J1 and J0 -> J1 -> J2 -> K1 and K0 -> K1 -> K2, 1078 * where I1, J1 and K1 are created in parallel. 1079 * 1080 * <p>Creation is synchronized by injection of {@link S}, first thread would block until second 1081 * would be inside a singleton creation as well. 1082 * 1083 * <p>Verifies that provision results in an error, that spans two threads and 1084 * has a dependency cycle. 1085 */ 1086 1087 public void testUnresolvableSingletonCircularDependencyErrorMessage() throws Exception { 1088 final Provider<S> provider = new SBarrierProvider(3); 1089 final Injector injector = Guice.createInjector(new AbstractModule() { 1090 @Override 1091 protected void configure() { 1092 bind(S.class).toProvider(provider); 1093 } 1094 }); 1095 1096 Future<I0> firstThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<I0>() { 1097 public I0 call() { 1098 Thread.currentThread().setName("I0.class"); 1099 return injector.getInstance(I0.class); 1100 } 1101 }); 1102 Future<J0> secondThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<J0>() { 1103 public J0 call() { 1104 Thread.currentThread().setName("J0.class"); 1105 return injector.getInstance(J0.class); 1106 } 1107 }); 1108 Future<K0> thirdThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<K0>() { 1109 public K0 call() { 1110 Thread.currentThread().setName("K0.class"); 1111 return injector.getInstance(K0.class); 1112 } 1113 }); 1114 1115 // using separate threads to avoid potential deadlock on the main thread 1116 // waiting twice as much to be sure that both would time out in their respective barriers 1117 Throwable firstException = null; 1118 Throwable secondException = null; 1119 Throwable thirdException = null; 1120 try { 1121 firstThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); 1122 fail(); 1123 } catch (ExecutionException e) { 1124 firstException = e.getCause(); 1125 } 1126 try { 1127 secondThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); 1128 fail(); 1129 } catch (ExecutionException e) { 1130 secondException = e.getCause(); 1131 } 1132 try { 1133 thirdThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); 1134 fail(); 1135 } catch (ExecutionException e) { 1136 thirdException = e.getCause(); 1137 } 1138 1139 // verification of error messages generated 1140 assertEquals(firstException.getClass(), ProvisionException.class); 1141 assertEquals(secondException.getClass(), ProvisionException.class); 1142 assertEquals(thirdException.getClass(), ProvisionException.class); 1143 List<String> errorMessages = Lists.newArrayList( 1144 String.format("%s\n%s\n%s", 1145 firstException.getMessage(), secondException.getMessage(), thirdException.getMessage()) 1146 .split("\\n\\n")); 1147 Collections.sort(errorMessages, new Comparator<String>() { 1148 @Override 1149 public int compare(String s1, String s2) { 1150 return s2.length() - s1.length(); 1151 } 1152 }); 1153 // this is brittle, but turns out that second to longest message spans all threads 1154 String errorMessage = errorMessages.get(1); 1155 assertContains(errorMessage, 1156 "Encountered circular dependency spanning several threads. Tried proxying " 1157 + this.getClass().getName()); 1158 assertFalse("Both I0 and J0 can not be a part of a dependency cycle", 1159 errorMessage.contains(I0.class.getName()) && errorMessage.contains(J0.class.getName())); 1160 assertFalse("Both J0 and K0 can not be a part of a dependency cycle", 1161 errorMessage.contains(J0.class.getName()) && errorMessage.contains(K0.class.getName())); 1162 assertFalse("Both K0 and I0 can not be a part of a dependency cycle", 1163 errorMessage.contains(K0.class.getName()) && errorMessage.contains(I0.class.getName())); 1164 1165 List<String> firstErrorLineForThread = new ArrayList<String>(); 1166 boolean addNextLine = false; 1167 for (String errorLine : errorMessage.split("\\n")) { 1168 if (errorLine.contains("in thread")) { 1169 addNextLine = true; 1170 firstErrorLineForThread.add(errorLine); 1171 } else if (addNextLine) { 1172 addNextLine = false; 1173 firstErrorLineForThread.add(errorLine); 1174 } 1175 } 1176 assertEquals("we expect to see [T1, $A, T2, $B, T3, $C, T1, $A]", 1177 8, firstErrorLineForThread.size()); 1178 assertEquals("first four elements should be different", 1179 6, new HashSet<String>(firstErrorLineForThread.subList(0, 6)).size()); 1180 assertEquals(firstErrorLineForThread.get(6), firstErrorLineForThread.get(0)); 1181 assertEquals(firstErrorLineForThread.get(7), firstErrorLineForThread.get(1)); 1182 assertFalse("K0 thread could not be blocked by J0", 1183 firstErrorLineForThread.get(0).contains("J0") 1184 && firstErrorLineForThread.get(2).contains("K0")); 1185 assertFalse("J0 thread could not be blocked by I0", 1186 firstErrorLineForThread.get(0).contains("I0") 1187 && firstErrorLineForThread.get(2).contains("J0")); 1188 assertFalse("I0 thread could not be blocked by K0", 1189 firstErrorLineForThread.get(0).contains("K0") 1190 && firstErrorLineForThread.get(2).contains("I0")); 1191 } 1192 } 1193