blob: cfc9490114549174079f6173f4b90c191680ed66 [file] [log] [blame]
/*
* Copyright (C) 2007 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.spi;
import static com.google.inject.Asserts.assertContains;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.BindingAnnotation;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Provides;
import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.Errors;
import com.google.inject.internal.InternalFlags;
import com.google.inject.internal.ProviderMethod;
import com.google.inject.internal.ProviderMethodsModule;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.google.inject.util.Providers;
import com.google.inject.util.Types;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import junit.framework.TestCase;
/** @author crazybob@google.com (Bob Lee) */
@SuppressWarnings("ProvidesMethodOutsideOfModule")
public class ProviderMethodsTest extends TestCase implements Module {
@SuppressWarnings("unchecked")
public void testProviderMethods() {
Injector injector = Guice.createInjector(this);
Bob bob = injector.getInstance(Bob.class);
assertEquals("A Bob", bob.getName());
Bob clone = injector.getInstance(Bob.class);
assertEquals("A Bob", clone.getName());
assertNotSame(bob, clone);
assertSame(bob.getDaughter(), clone.getDaughter());
Key soleBobKey = Key.get(Bob.class, Sole.class);
assertSame(injector.getInstance(soleBobKey), injector.getInstance(soleBobKey));
}
@Override
public void configure(Binder binder) {}
interface Bob {
String getName();
Dagny getDaughter();
}
interface Dagny {
int getAge();
}
@Provides
Bob provideBob(final Dagny dagny) {
return new Bob() {
@Override
public String getName() {
return "A Bob";
}
@Override
public Dagny getDaughter() {
return dagny;
}
};
}
@Provides
@Singleton
@Sole
Bob provideSoleBob(final Dagny dagny) {
return new Bob() {
@Override
public String getName() {
return "Only Bob";
}
@Override
public Dagny getDaughter() {
return dagny;
}
};
}
@Provides
@Singleton
Dagny provideDagny() {
return new Dagny() {
@Override
public int getAge() {
return 1;
}
};
}
@Retention(RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@BindingAnnotation
@interface Sole {}
public void testCircularDependency() {
Injector injector =
Guice.createInjector(
new AbstractModule() {
@Provides
Foo newFoo(final Bar bar) {
return new Foo() {
@Override
public Bar getBar() {
return bar;
}
@Override
public int getI() {
return 5;
}
};
}
@Provides
Bar newBar(final Foo foo) {
return new Bar() {
@Override
public Foo getFoo() {
return foo;
}
@Override
public int getI() {
return 10;
}
};
}
});
Foo foo = injector.getInstance(Foo.class);
assertEquals(5, foo.getI());
assertEquals(10, foo.getBar().getI());
assertEquals(5, foo.getBar().getFoo().getI());
}
public interface Foo {
Bar getBar();
int getI();
}
public interface Bar {
Foo getFoo();
int getI();
}
public void testMultipleBindingAnnotations() {
try {
Guice.createInjector(
new AbstractModule() {
@Provides
@Named("A")
@Blue
public String provideString() {
return "a";
}
});
fail();
} catch (CreationException expected) {
assertContains(
expected.getMessage(),
"more than one annotation annotated with @BindingAnnotation:",
"Named",
"Blue",
"at " + getClass().getName(),
".provideString(ProviderMethodsTest.java:");
}
}
@Retention(RUNTIME)
@BindingAnnotation
@interface Blue {}
public void testGenericProviderMethods() {
Injector injector =
Guice.createInjector(new ProvideTs<String>("A", "B") {}, new ProvideTs<Integer>(1, 2) {});
assertEquals("A", injector.getInstance(Key.get(String.class, Names.named("First"))));
assertEquals("B", injector.getInstance(Key.get(String.class, Names.named("Second"))));
assertEquals(
ImmutableSet.of("A", "B"), injector.getInstance(Key.get(Types.setOf(String.class))));
assertEquals(1, injector.getInstance(Key.get(Integer.class, Names.named("First"))).intValue());
assertEquals(2, injector.getInstance(Key.get(Integer.class, Names.named("Second"))).intValue());
assertEquals(ImmutableSet.of(1, 2), injector.getInstance(Key.get(Types.setOf(Integer.class))));
}
abstract class ProvideTs<T> extends AbstractModule {
final T first;
final T second;
protected ProvideTs(T first, T second) {
this.first = first;
this.second = second;
}
@Named("First")
@Provides
T provideFirst() {
return first;
}
@Named("Second")
@Provides
T provideSecond() {
return second;
}
@Provides
Set<T> provideBoth(@Named("First") T first, @Named("Second") T second) {
return ImmutableSet.of(first, second);
}
}
public void testAutomaticProviderMethods() {
Injector injector =
Guice.createInjector(
(Module)
new AbstractModule() {
private int next = 1;
@Provides
@Named("count")
public Integer provideCount() {
return next++;
}
});
assertEquals(1, injector.getInstance(Key.get(Integer.class, Names.named("count"))).intValue());
assertEquals(2, injector.getInstance(Key.get(Integer.class, Names.named("count"))).intValue());
assertEquals(3, injector.getInstance(Key.get(Integer.class, Names.named("count"))).intValue());
}
/**
* If the user installs provider methods for the module manually, that shouldn't cause a double
* binding of the provider methods' types.
*/
public void testAutomaticProviderMethodsDoNotCauseDoubleBinding() {
Module installsSelf =
new AbstractModule() {
@Override
protected void configure() {
install(this);
bind(Integer.class).toInstance(5);
}
@Provides
public String provideString(Integer count) {
return "A" + count;
}
};
Injector injector = Guice.createInjector(installsSelf);
assertEquals("A5", injector.getInstance(String.class));
}
public void testWildcardProviderMethods() {
final List<String> strings = ImmutableList.of("A", "B", "C");
final List<Number> numbers = ImmutableList.<Number>of(1, 2, 3);
Injector injector =
Guice.createInjector(
new AbstractModule() {
@Override
protected void configure() {
@SuppressWarnings("unchecked")
Key<List<? super Integer>> listOfSupertypesOfInteger =
(Key<List<? super Integer>>)
Key.get(Types.listOf(Types.supertypeOf(Integer.class)));
bind(listOfSupertypesOfInteger).toInstance(numbers);
}
@Provides
public List<? extends CharSequence> provideCharSequences() {
return strings;
}
@Provides
public Class<?> provideType() {
return Float.class;
}
});
assertSame(strings, injector.getInstance(HasWildcardInjection.class).charSequences);
assertSame(numbers, injector.getInstance(HasWildcardInjection.class).numbers);
assertSame(Float.class, injector.getInstance(HasWildcardInjection.class).type);
}
static class HasWildcardInjection {
@Inject List<? extends CharSequence> charSequences;
@Inject List<? super Integer> numbers;
@Inject Class<?> type;
}
public void testProviderMethodDependenciesAreExposed() throws Exception {
Module module =
new AbstractModule() {
@Override
protected void configure() {
bind(Integer.class).toInstance(50);
bindConstant().annotatedWith(Names.named("units")).to("Kg");
}
@Provides
@Named("weight")
String provideWeight(Integer count, @Named("units") String units) {
return count + units;
}
};
Injector injector = Guice.createInjector(module);
ProviderInstanceBinding<?> binding =
(ProviderInstanceBinding<?>)
injector.getBinding(Key.get(String.class, Names.named("weight")));
Method method =
module.getClass().getDeclaredMethod("provideWeight", Integer.class, String.class);
InjectionPoint point = new InjectionPoint(TypeLiteral.get(module.getClass()), method, false);
assertEquals(
ImmutableSet.<Dependency<?>>of(
new Dependency<Integer>(point, Key.get(Integer.class), false, 0),
new Dependency<String>(point, Key.get(String.class, Names.named("units")), false, 1)),
binding.getDependencies());
}
public void testNonModuleProviderMethods() {
final Object methodsObject =
new Object() {
@Provides
@Named("foo")
String provideFoo() {
return "foo-value";
}
};
Module module =
new AbstractModule() {
@Override
protected void configure() {
install(ProviderMethodsModule.forObject(methodsObject));
}
};
Injector injector = Guice.createInjector(module);
Key<String> key = Key.get(String.class, Names.named("foo"));
assertEquals("foo-value", injector.getInstance(key));
// Test the provider method object itself. This makes sure getInstance works, since GIN uses it
List<Element> elements = Elements.getElements(module);
assertEquals(1, elements.size());
Element element = elements.get(0);
assertTrue(
element + " instanceof ProviderInstanceBinding",
element instanceof ProviderInstanceBinding);
ProviderInstanceBinding binding = (ProviderInstanceBinding) element;
javax.inject.Provider provider = binding.getUserSuppliedProvider();
assertTrue(provider instanceof ProviderMethod);
assertEquals(methodsObject, ((ProviderMethod) provider).getInstance());
assertSame(provider, binding.getProviderInstance());
}
public void testVoidProviderMethods() {
try {
Guice.createInjector(
new AbstractModule() {
@Provides
void provideFoo() {}
});
fail();
} catch (CreationException expected) {
assertContains(
expected.getMessage(),
"1) Provider methods must return a value. Do not return void.",
getClass().getName(),
".provideFoo(ProviderMethodsTest.java:");
}
}
public void testInjectsJustOneLogger() {
AtomicReference<Logger> loggerRef = new AtomicReference<>();
Injector injector = Guice.createInjector(new FooModule(loggerRef));
assertNull(loggerRef.get());
injector.getInstance(Integer.class);
Logger lastLogger = loggerRef.getAndSet(null);
assertNotNull(lastLogger);
injector.getInstance(Integer.class);
assertSame(lastLogger, loggerRef.get());
assertEquals(FooModule.class.getName(), lastLogger.getName());
}
private static class FooModule extends AbstractModule {
private final AtomicReference<Logger> loggerRef;
public FooModule(AtomicReference<Logger> loggerRef) {
this.loggerRef = loggerRef;
}
@SuppressWarnings("unused")
@Provides
Integer foo(Logger logger) {
loggerRef.set(logger);
return 42;
}
}
public void testSpi() throws Exception {
Module m1 =
new AbstractModule() {
@Provides
@Named("foo")
String provideFoo(Integer dep) {
return "foo";
}
};
Module m2 =
new AbstractModule() {
@Provides
Integer provideInt(@Named("foo") String dep) {
return 42;
}
};
Injector injector = Guice.createInjector(m1, m2);
Binding<String> stringBinding = injector.getBinding(Key.get(String.class, Names.named("foo")));
ProvidesMethodBinding<String> stringMethod =
stringBinding.acceptTargetVisitor(new BindingCapturer<String>());
assertEquals(m1, stringMethod.getEnclosingInstance());
assertEquals(
m1.getClass().getDeclaredMethod("provideFoo", Integer.class), stringMethod.getMethod());
assertEquals(
((HasDependencies) stringBinding).getDependencies(), stringMethod.getDependencies());
assertEquals(Key.get(String.class, Names.named("foo")), stringMethod.getKey());
Binding<Integer> intBinding = injector.getBinding(Integer.class);
ProvidesMethodBinding<Integer> intMethod =
intBinding.acceptTargetVisitor(new BindingCapturer<Integer>());
assertEquals(m2, intMethod.getEnclosingInstance());
assertEquals(
m2.getClass().getDeclaredMethod("provideInt", String.class), intMethod.getMethod());
assertEquals(((HasDependencies) intBinding).getDependencies(), intMethod.getDependencies());
assertEquals(Key.get(Integer.class), intMethod.getKey());
}
private static class BindingCapturer<T>
extends DefaultBindingTargetVisitor<T, ProvidesMethodBinding<T>>
implements ProvidesMethodTargetVisitor<T, ProvidesMethodBinding<T>> {
@Override
@SuppressWarnings("unchecked")
public ProvidesMethodBinding<T> visit(
ProvidesMethodBinding<? extends T> providesMethodBinding) {
return (ProvidesMethodBinding<T>) providesMethodBinding;
}
@Override
protected ProvidesMethodBinding<T> visitOther(Binding<? extends T> binding) {
throw new IllegalStateException("unexpected visit of: " + binding);
}
}
public void testProvidesMethodVisibility() {
Injector injector = Guice.createInjector(new VisibilityModule());
assertEquals(42, injector.getInstance(Integer.class).intValue());
assertEquals(42L, injector.getInstance(Long.class).longValue());
assertEquals(42D, injector.getInstance(Double.class).doubleValue(), 0.0);
assertEquals(42F, injector.getInstance(Float.class).floatValue(), 0.0f);
}
private static class VisibilityModule extends AbstractModule {
@SuppressWarnings("unused")
@Provides
Integer foo() {
return 42;
}
@SuppressWarnings("unused")
@Provides
private Long bar() {
return 42L;
}
@SuppressWarnings("unused")
@Provides
protected Double baz() {
return 42D;
}
@SuppressWarnings("unused")
@Provides
public Float quux() {
return 42F;
}
}
public void testProvidesMethodInheritenceHierarchy() {
try {
Guice.createInjector(new Sub1Module(), new Sub2Module());
fail("Expected injector creation failure");
} catch (CreationException expected) {
// both of our super class bindings cause errors
assertContains(
expected.getMessage(),
"A binding to java.lang.Long was already configured",
"A binding to java.lang.Integer was already configured");
}
}
public void testProvidesMethodsDefinedInSuperClass() {
Injector injector = Guice.createInjector(new Sub1Module());
assertEquals(42, injector.getInstance(Integer.class).intValue());
assertEquals(42L, injector.getInstance(Long.class).longValue());
assertEquals(42D, injector.getInstance(Double.class).doubleValue(), 0.0);
}
private static class BaseModule extends AbstractModule {
@Provides
Integer foo() {
return 42;
}
@Provides
Long bar() {
return 42L;
}
}
private static class Sub1Module extends BaseModule {
@Provides
Double baz() {
return 42D;
}
}
private static class Sub2Module extends BaseModule {
@Provides
Float quux() {
return 42F;
}
}
/*if[AOP]*/
public void testShareFastClass() {
CallerInspecterModule module = new CallerInspecterModule();
Guice.createInjector(Stage.PRODUCTION, module);
assertEquals(module.fooCallerClass, module.barCallerClass);
assertTrue(module.fooCallerClass.contains("$$FastClassByGuice$$"));
}
private static class CallerInspecterModule extends AbstractModule {
// start them off as unequal
String barCallerClass = "not_set_bar";
String fooCallerClass = "not_set_foo";
@Provides
@Singleton
Integer foo() {
this.fooCallerClass = new Exception().getStackTrace()[1].getClassName();
return 42;
}
@Provides
@Singleton
Long bar() {
this.barCallerClass = new Exception().getStackTrace()[1].getClassName();
return 42L;
}
}
public void testShareFastClassWithSuperClass() {
CallerInspecterSubClassModule module = new CallerInspecterSubClassModule();
Guice.createInjector(Stage.PRODUCTION, module);
assertEquals(
"Expected provider methods in the same class to share fastclass classes",
module.fooCallerClass,
module.barCallerClass);
assertFalse(
"Did not expect provider methods in the subclasses to share fastclass classes "
+ "with their parent classes",
module.bazCallerClass.equals(module.barCallerClass));
}
private static class CallerInspecterSubClassModule extends CallerInspecterModule {
String bazCallerClass;
@Override
protected void configure() {}
@Provides
@Singleton
Double baz() {
this.bazCallerClass = new Exception().getStackTrace()[1].getClassName();
return 42D;
}
}
/*end[AOP]*/
static class SuperClassModule extends AbstractModule {
@Provides
Number providerMethod() {
return 1D;
}
@Provides
@Named("rawlist")
List rawProvider(@Named("list") List<String> f) {
return f;
}
@Provides
@Named("unrawlist")
List<String> rawParameterProvider(@Named("rawlist") List f) {
return f;
}
@Provides
@Named("list")
List<String> annotatedGenericProviderMethod() {
return new ArrayList<String>();
}
@Provides
@Named("collection")
Collection<String> annotatedGenericParameterProviderMethod(@Named("list") List<String> foo) {
return foo;
}
@Provides
private String privateProviderMethod() {
return "hello";
}
}
public void testOverrideProviderMethod_overrideHasProvides() {
class SubClassModule extends SuperClassModule {
@Override
@Provides
Number providerMethod() {
return 2D;
}
}
try {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
assertContains(
e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
}
}
public void testOverrideProviderMethod_overrideHasProvides_withNewAnnotation() {
class SubClassModule extends SuperClassModule {
@Override
@Provides
@Named("foo")
Number providerMethod() {
return 2D;
}
}
try {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
assertContains(
e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
}
}
public void testOverrideProviderMethod_overrideDoesntHaveProvides() {
class SubClassModule extends SuperClassModule {
@Override
Number providerMethod() {
return 2D;
}
}
try {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
assertContains(
e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
}
}
public void testOverrideProviderMethod_overrideDoesntHaveProvides_withNewAnnotation() {
class SubClassModule extends SuperClassModule {
@Override
@Named("foo")
Number providerMethod() {
return 2D;
}
}
try {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
assertContains(
e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
}
}
public void testOverrideProviderMethod_covariantOverrideDoesntHaveProvides() {
class SubClassModule extends SuperClassModule {
@Override
Double providerMethod() {
return 2D;
}
}
try {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
assertContains(
e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
}
}
public void testOverrideProviderMethod_covariantOverrideHasProvides() {
class SubClassModule extends SuperClassModule {
@Override
@Provides
Double providerMethod() {
return 2D;
}
}
try {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
assertContains(
e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".providerMethod()",
"overridden by: " + SubClassModule.class.getName() + ".providerMethod()");
}
}
public void testOverrideProviderMethod_fakeOverridePrivateMethod() {
class SubClassModule extends SuperClassModule {
// not actually an override, just looks like it
String privateProviderMethod() {
return "sub";
}
}
assertEquals("hello", Guice.createInjector(new SubClassModule()).getInstance(String.class));
}
public void testOverrideProviderMethod_subclassRawTypes_returnType() {
class SubClassModule extends SuperClassModule {
@Override
List annotatedGenericProviderMethod() {
return super.annotatedGenericProviderMethod();
}
}
try {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
assertContains(
e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: "
+ SuperClassModule.class.getName()
+ ".annotatedGenericProviderMethod()",
"overridden by: " + SubClassModule.class.getName() + ".annotatedGenericProviderMethod()");
}
}
public void testOverrideProviderMethod_subclassRawTypes_parameterType() {
class SubClassModule extends SuperClassModule {
@Override
Collection<String> annotatedGenericParameterProviderMethod(List foo) {
return super.annotatedGenericParameterProviderMethod(foo);
}
}
try {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
assertContains(
e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: "
+ SuperClassModule.class.getName()
+ ".annotatedGenericParameterProviderMethod()",
"overridden by: "
+ SubClassModule.class.getName()
+ ".annotatedGenericParameterProviderMethod()");
}
}
public void testOverrideProviderMethod_superclassRawTypes_returnType() {
class SubClassModule extends SuperClassModule {
// remove the rawtype from the override
@Override
List<String> rawProvider(List<String> f) {
return f;
}
}
try {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
assertContains(
e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + SuperClassModule.class.getName() + ".rawProvider()",
"overridden by: " + SubClassModule.class.getName() + ".rawProvider()");
}
}
abstract static class GenericSuperModule<T> extends AbstractModule {
@Provides
String provide(T thing) {
return thing.toString();
}
}
// This is a tricky case where signatures don't match, but it is an override (facilitated via a
// bridge method)
public void testOverrideProviderMethod_erasureBasedOverrides() {
class SubClassModule extends GenericSuperModule<Integer> {
@Override
String provide(Integer thing) {
return thing.toString();
}
@Override
protected void configure() {
bind(Integer.class).toInstance(3);
}
}
try {
Guice.createInjector(new SubClassModule());
fail();
} catch (CreationException e) {
assertContains(
e.getMessage(),
"Overriding @Provides methods is not allowed.",
"@Provides method: " + GenericSuperModule.class.getName() + ".provide()",
"overridden by: " + SubClassModule.class.getName() + ".provide()");
}
}
static class RestrictedSuper extends AbstractModule {
@Provides
public String provideFoo() {
return "foo";
}
}
public static class ExposedSub extends RestrictedSuper {}
public void testOverrideProviderMethod_increasedVisibility() {
// ensure we don't detect the synthetic provideFoo method in ExposedSub as an override (it is,
// but since it is synthetic it would be annoying to throw an error on it).
assertEquals("foo", Guice.createInjector(new ExposedSub()).getInstance(String.class));
}
interface ProviderInterface<T> {
T getT();
}
static class ModuleImpl extends AbstractModule implements ProviderInterface<String> {
@Override
@Provides
public String getT() {
return "string";
}
@Provides
public Object getObject() {
return new Object();
}
/* javac will synthesize a bridge method for getT with the types erased, equivalent to:
* @Provides public Object getT() { ... }
*/
}
public void testIgnoreSyntheticBridgeMethods() {
Guice.createInjector(new ModuleImpl());
}
public void testScopedProviderMethodThrowsException() {
Injector injector =
Guice.createInjector(
new AbstractModule() {
@Provides
@Singleton
int provideInt() {
throw new RuntimeException("boom");
}
});
Provider<Integer> intProvider = injector.getProvider(Integer.class);
try {
intProvider.get();
fail();
} catch (ProvisionException pe) {
// by default assertContains asserts that the last item doesn't repeat... which is the main
// thing we are testing for
assertContains(pe.getMessage(), "java.lang.RuntimeException: boom", "provideInt");
}
}
public void testNullability() throws Exception {
Module module =
new AbstractModule() {
@Override
protected void configure() {
bind(String.class).toProvider(Providers.<String>of(null));
}
@SuppressWarnings("unused")
@Provides
Integer fail(String foo) {
return 1;
}
@SuppressWarnings("unused")
@Provides
Long succeed(@Nullable String foo) {
return 2L;
}
};
Injector injector = Guice.createInjector(module);
InjectionPoint fooPoint =
InjectionPoint.forMethod(
module.getClass().getDeclaredMethod("fail", String.class),
TypeLiteral.get(module.getClass()));
Dependency<?> fooDependency = Iterables.getOnlyElement(fooPoint.getDependencies());
runNullableTest(injector, fooDependency, module);
injector.getInstance(Long.class);
}
public void testModuleBindings() throws Exception {
Module module =
new AbstractModule() {
@Provides
Integer fail() {
return 1;
}
};
// sanity check that the injector works
Injector injector = Guice.createInjector(module);
assertEquals(1, injector.getInstance(Integer.class).intValue());
ProviderInstanceBinding injectorBinding =
(ProviderInstanceBinding) injector.getBinding(Integer.class);
assertEquals(1, injectorBinding.getUserSuppliedProvider().get());
ProviderInstanceBinding moduleBinding =
(ProviderInstanceBinding) Iterables.getOnlyElement(Elements.getElements(module));
try {
moduleBinding.getUserSuppliedProvider().get();
fail();
} catch (IllegalStateException ise) {
assertEquals(
"This Provider cannot be used until the Injector has been created.", ise.getMessage());
}
}
private void runNullableTest(Injector injector, Dependency<?> dependency, Module module) {
switch (InternalFlags.getNullableProvidesOption()) {
case ERROR:
validateNullableFails(injector, module);
break;
case IGNORE:
validateNullableIgnored(injector);
break;
case WARN:
validateNullableWarns(injector, dependency);
break;
}
}
private void validateNullableFails(Injector injector, Module module) {
try {
injector.getInstance(Integer.class);
fail();
} catch (ProvisionException expected) {
assertContains(
expected.getMessage(),
"1) null returned by binding at " + module.getClass().getName() + ".configure(",
"but the 1st parameter of " + module.getClass().getName() + ".fail(",
"is not @Nullable",
"while locating java.lang.String",
"for the 1st parameter of " + module.getClass().getName() + ".fail(",
"while locating java.lang.Integer");
assertEquals(1, expected.getErrorMessages().size());
}
}
private void validateNullableIgnored(Injector injector) {
injector.getInstance(Integer.class); // no exception
}
private void validateNullableWarns(Injector injector, Dependency<?> dependency) {
final List<LogRecord> logRecords = Lists.newArrayList();
final Handler fakeHandler =
new Handler() {
@Override
public void publish(LogRecord logRecord) {
logRecords.add(logRecord);
}
@Override
public void flush() {}
@Override
public void close() throws SecurityException {}
};
Logger.getLogger(Guice.class.getName()).addHandler(fakeHandler);
try {
injector.getInstance(Integer.class); // no exception, but assert it does log.
LogRecord record = Iterables.getOnlyElement(logRecords);
assertEquals(
"Guice injected null into {0} (a {1}), please mark it @Nullable."
+ " Use -Dguice_check_nullable_provides_params=ERROR to turn this into an"
+ " error.",
record.getMessage());
assertEquals(Errors.convert(dependency.getKey()), record.getParameters()[1]);
} finally {
Logger.getLogger(Guice.class.getName()).removeHandler(fakeHandler);
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface Nullable {}
}