522 lines
21 KiB
Java
522 lines
21 KiB
Java
/*
|
|
* Copyright (C) 2014 The Dagger Authors.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
// TODO(beder): Merge the error-handling tests with the ModuleFactoryGeneratorTest.
|
|
package dagger.internal.codegen;
|
|
|
|
import static com.google.common.truth.Truth.assertAbout;
|
|
import static com.google.testing.compile.CompilationSubject.assertThat;
|
|
import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
|
|
import static dagger.internal.codegen.Compilers.daggerCompiler;
|
|
import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatMethodInUnannotatedClass;
|
|
import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatProductionModuleMethod;
|
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|
|
|
import com.google.common.util.concurrent.ListenableFuture;
|
|
import com.google.testing.compile.Compilation;
|
|
import com.google.testing.compile.JavaFileObjects;
|
|
import java.lang.annotation.Retention;
|
|
import javax.inject.Qualifier;
|
|
import javax.tools.JavaFileObject;
|
|
import org.junit.Test;
|
|
import org.junit.runner.RunWith;
|
|
import org.junit.runners.JUnit4;
|
|
|
|
@RunWith(JUnit4.class)
|
|
public class ProducerModuleFactoryGeneratorTest {
|
|
|
|
@Test public void producesMethodNotInModule() {
|
|
assertThatMethodInUnannotatedClass("@Produces String produceString() { return null; }")
|
|
.hasError("@Produces methods can only be present within a @ProducerModule");
|
|
}
|
|
|
|
@Test public void producesMethodAbstract() {
|
|
assertThatProductionModuleMethod("@Produces abstract String produceString();")
|
|
.hasError("@Produces methods cannot be abstract");
|
|
}
|
|
|
|
@Test public void producesMethodPrivate() {
|
|
assertThatProductionModuleMethod("@Produces private String produceString() { return null; }")
|
|
.hasError("@Produces methods cannot be private");
|
|
}
|
|
|
|
@Test public void producesMethodReturnVoid() {
|
|
assertThatProductionModuleMethod("@Produces void produceNothing() {}")
|
|
.hasError("@Produces methods must return a value (not void)");
|
|
}
|
|
|
|
@Test
|
|
public void producesProvider() {
|
|
assertThatProductionModuleMethod("@Produces Provider<String> produceProvider() {}")
|
|
.hasError("@Produces methods must not return framework types");
|
|
}
|
|
|
|
@Test
|
|
public void producesLazy() {
|
|
assertThatProductionModuleMethod("@Produces Lazy<String> produceLazy() {}")
|
|
.hasError("@Produces methods must not return framework types");
|
|
}
|
|
|
|
@Test
|
|
public void producesMembersInjector() {
|
|
assertThatProductionModuleMethod(
|
|
"@Produces MembersInjector<String> produceMembersInjector() {}")
|
|
.hasError("@Produces methods must not return framework types");
|
|
}
|
|
|
|
@Test
|
|
public void producesProducer() {
|
|
assertThatProductionModuleMethod("@Produces Producer<String> produceProducer() {}")
|
|
.hasError("@Produces methods must not return framework types");
|
|
}
|
|
|
|
@Test
|
|
public void producesProduced() {
|
|
assertThatProductionModuleMethod("@Produces Produced<String> produceProduced() {}")
|
|
.hasError("@Produces methods must not return framework types");
|
|
}
|
|
|
|
@Test public void producesMethodReturnRawFuture() {
|
|
assertThatProductionModuleMethod("@Produces ListenableFuture produceRaw() {}")
|
|
.importing(ListenableFuture.class)
|
|
.hasError("@Produces methods cannot return a raw ListenableFuture");
|
|
}
|
|
|
|
@Test public void producesMethodReturnWildcardFuture() {
|
|
assertThatProductionModuleMethod("@Produces ListenableFuture<?> produceRaw() {}")
|
|
.importing(ListenableFuture.class)
|
|
.hasError(
|
|
"@Produces methods can return only a primitive, an array, a type variable, "
|
|
+ "a declared type, or a ListenableFuture of one of those types");
|
|
}
|
|
|
|
@Test public void producesMethodWithTypeParameter() {
|
|
assertThatProductionModuleMethod("@Produces <T> String produceString() { return null; }")
|
|
.hasError("@Produces methods may not have type parameters");
|
|
}
|
|
|
|
@Test public void producesMethodSetValuesWildcard() {
|
|
assertThatProductionModuleMethod(
|
|
"@Produces @ElementsIntoSet Set<?> produceWildcard() { return null; }")
|
|
.hasError(
|
|
"@Produces methods can return only a primitive, an array, a type variable, "
|
|
+ "a declared type, or a ListenableFuture of one of those types");
|
|
}
|
|
|
|
@Test public void producesMethodSetValuesRawSet() {
|
|
assertThatProductionModuleMethod(
|
|
"@Produces @ElementsIntoSet Set produceSomething() { return null; }")
|
|
.hasError("@Produces methods annotated with @ElementsIntoSet cannot return a raw Set");
|
|
}
|
|
|
|
@Test public void producesMethodSetValuesNotASet() {
|
|
assertThatProductionModuleMethod(
|
|
"@Produces @ElementsIntoSet List<String> produceStrings() { return null; }")
|
|
.hasError(
|
|
"@Produces methods of type set values must return a Set or ListenableFuture of Set");
|
|
}
|
|
|
|
@Test public void producesMethodSetValuesWildcardInFuture() {
|
|
assertThatProductionModuleMethod(
|
|
"@Produces @ElementsIntoSet "
|
|
+ "ListenableFuture<Set<?>> produceWildcard() { return null; }")
|
|
.importing(ListenableFuture.class)
|
|
.hasError(
|
|
"@Produces methods can return only a primitive, an array, a type variable, "
|
|
+ "a declared type, or a ListenableFuture of one of those types");
|
|
}
|
|
|
|
@Test public void producesMethodSetValuesFutureRawSet() {
|
|
assertThatProductionModuleMethod(
|
|
"@Produces @ElementsIntoSet ListenableFuture<Set> produceSomething() { return null; }")
|
|
.importing(ListenableFuture.class)
|
|
.hasError("@Produces methods annotated with @ElementsIntoSet cannot return a raw Set");
|
|
}
|
|
|
|
@Test public void producesMethodSetValuesFutureNotASet() {
|
|
assertThatProductionModuleMethod(
|
|
"@Produces @ElementsIntoSet "
|
|
+ "ListenableFuture<List<String>> produceStrings() { return null; }")
|
|
.importing(ListenableFuture.class)
|
|
.hasError(
|
|
"@Produces methods of type set values must return a Set or ListenableFuture of Set");
|
|
}
|
|
|
|
@Test public void multipleProducesMethodsWithSameName() {
|
|
JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
|
|
"package test;",
|
|
"",
|
|
"import dagger.producers.ProducerModule;",
|
|
"import dagger.producers.Produces;",
|
|
"",
|
|
"@ProducerModule",
|
|
"final class TestModule {",
|
|
" @Produces Object produce(int i) {",
|
|
" return i;",
|
|
" }",
|
|
"",
|
|
" @Produces String produce() {",
|
|
" return \"\";",
|
|
" }",
|
|
"}");
|
|
String errorMessage =
|
|
"Cannot have more than one binding method with the same name in a single module";
|
|
Compilation compilation = daggerCompiler().compile(moduleFile);
|
|
assertThat(compilation).failed();
|
|
assertThat(compilation).hadErrorContaining(errorMessage).inFile(moduleFile).onLine(8);
|
|
assertThat(compilation).hadErrorContaining(errorMessage).inFile(moduleFile).onLine(12);
|
|
}
|
|
|
|
@Test
|
|
public void producesMethodThrowsThrowable() {
|
|
assertThatProductionModuleMethod("@Produces int produceInt() throws Throwable { return 0; }")
|
|
.hasError(
|
|
"@Produces methods may only throw unchecked exceptions or exceptions subclassing "
|
|
+ "Exception");
|
|
}
|
|
|
|
@Test public void producesMethodWithScope() {
|
|
assertThatProductionModuleMethod("@Produces @Singleton String str() { return \"\"; }")
|
|
.hasError("@Produces methods cannot be scoped");
|
|
}
|
|
|
|
@Test
|
|
public void privateModule() {
|
|
JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.Enclosing",
|
|
"package test;",
|
|
"",
|
|
"import dagger.producers.ProducerModule;",
|
|
"",
|
|
"final class Enclosing {",
|
|
" @ProducerModule private static final class PrivateModule {",
|
|
" }",
|
|
"}");
|
|
Compilation compilation = daggerCompiler().compile(moduleFile);
|
|
assertThat(compilation).failed();
|
|
assertThat(compilation)
|
|
.hadErrorContaining("Modules cannot be private")
|
|
.inFile(moduleFile)
|
|
.onLine(6);
|
|
}
|
|
|
|
|
|
@Test
|
|
public void enclosedInPrivateModule() {
|
|
JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.Enclosing",
|
|
"package test;",
|
|
"",
|
|
"import dagger.producers.ProducerModule;",
|
|
"",
|
|
"final class Enclosing {",
|
|
" private static final class PrivateEnclosing {",
|
|
" @ProducerModule static final class TestModule {",
|
|
" }",
|
|
" }",
|
|
"}");
|
|
Compilation compilation = daggerCompiler().compile(moduleFile);
|
|
assertThat(compilation).failed();
|
|
assertThat(compilation)
|
|
.hadErrorContaining("Modules cannot be enclosed in private types")
|
|
.inFile(moduleFile)
|
|
.onLine(7);
|
|
}
|
|
|
|
@Test
|
|
public void includesNonModule() {
|
|
JavaFileObject xFile =
|
|
JavaFileObjects.forSourceLines("test.X", "package test;", "", "public final class X {}");
|
|
JavaFileObject moduleFile =
|
|
JavaFileObjects.forSourceLines(
|
|
"test.FooModule",
|
|
"package test;",
|
|
"",
|
|
"import dagger.producers.ProducerModule;",
|
|
"",
|
|
"@ProducerModule(includes = X.class)",
|
|
"public final class FooModule {",
|
|
"}");
|
|
Compilation compilation = daggerCompiler().compile(xFile, moduleFile);
|
|
assertThat(compilation).failed();
|
|
assertThat(compilation)
|
|
.hadErrorContaining(
|
|
"X is listed as a module, but is not annotated with one of @Module, @ProducerModule");
|
|
}
|
|
|
|
// TODO(ronshapiro): merge this with the equivalent test in ModuleFactoryGeneratorTest and make it
|
|
// parameterized
|
|
@Test
|
|
public void publicModuleNonPublicIncludes() {
|
|
JavaFileObject publicModuleFile = JavaFileObjects.forSourceLines("test.PublicModule",
|
|
"package test;",
|
|
"",
|
|
"import dagger.producers.ProducerModule;",
|
|
"",
|
|
"@ProducerModule(includes = {",
|
|
" BadNonPublicModule.class, OtherPublicModule.class, OkNonPublicModule.class",
|
|
"})",
|
|
"public final class PublicModule {",
|
|
"}");
|
|
JavaFileObject badNonPublicModuleFile =
|
|
JavaFileObjects.forSourceLines(
|
|
"test.BadNonPublicModule",
|
|
"package test;",
|
|
"",
|
|
"import dagger.producers.ProducerModule;",
|
|
"import dagger.producers.Produces;",
|
|
"",
|
|
"@ProducerModule",
|
|
"final class BadNonPublicModule {",
|
|
" @Produces",
|
|
" int produceInt() {",
|
|
" return 42;",
|
|
" }",
|
|
"}");
|
|
JavaFileObject okNonPublicModuleFile = JavaFileObjects.forSourceLines("test.OkNonPublicModule",
|
|
"package test;",
|
|
"",
|
|
"import dagger.producers.ProducerModule;",
|
|
"import dagger.producers.Produces;",
|
|
"",
|
|
"@ProducerModule",
|
|
"final class OkNonPublicModule {",
|
|
" @Produces",
|
|
" static String produceString() {",
|
|
" return \"foo\";",
|
|
" }",
|
|
"}");
|
|
JavaFileObject otherPublicModuleFile = JavaFileObjects.forSourceLines("test.OtherPublicModule",
|
|
"package test;",
|
|
"",
|
|
"import dagger.producers.ProducerModule;",
|
|
"",
|
|
"@ProducerModule",
|
|
"public final class OtherPublicModule {",
|
|
"}");
|
|
Compilation compilation =
|
|
daggerCompiler()
|
|
.compile(
|
|
publicModuleFile,
|
|
badNonPublicModuleFile,
|
|
okNonPublicModuleFile,
|
|
otherPublicModuleFile);
|
|
assertThat(compilation).failed();
|
|
assertThat(compilation)
|
|
.hadErrorContaining(
|
|
"This module is public, but it includes non-public (or effectively non-public) modules "
|
|
+ "(test.BadNonPublicModule) that have non-static, non-abstract binding methods. "
|
|
+ "Either reduce the visibility of this module, make the included modules public, "
|
|
+ "or make all of the binding methods on the included modules abstract or static.")
|
|
.inFile(publicModuleFile)
|
|
.onLine(8);
|
|
}
|
|
|
|
@Test public void argumentNamedModuleCompiles() {
|
|
JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
|
|
"package test;",
|
|
"",
|
|
"import dagger.producers.ProducerModule;",
|
|
"import dagger.producers.Produces;",
|
|
"",
|
|
"@ProducerModule",
|
|
"final class TestModule {",
|
|
" @Produces String produceString(int module) {",
|
|
" return null;",
|
|
" }",
|
|
"}");
|
|
Compilation compilation = daggerCompiler().compile(moduleFile);
|
|
assertThat(compilation).succeeded();
|
|
}
|
|
|
|
@Test public void singleProducesMethodNoArgsFuture() {
|
|
JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule",
|
|
"package test;",
|
|
"",
|
|
"import com.google.common.util.concurrent.ListenableFuture;",
|
|
"import dagger.producers.ProducerModule;",
|
|
"import dagger.producers.Produces;",
|
|
"",
|
|
"@ProducerModule",
|
|
"final class TestModule {",
|
|
" @Produces ListenableFuture<String> produceString() {",
|
|
" return null;",
|
|
" }",
|
|
"}");
|
|
JavaFileObject factoryFile =
|
|
JavaFileObjects.forSourceLines(
|
|
"TestModule_ProduceStringFactory",
|
|
"package test;",
|
|
"",
|
|
GeneratedLines.generatedImports(
|
|
"import com.google.common.util.concurrent.Futures;",
|
|
"import com.google.common.util.concurrent.ListenableFuture;",
|
|
"import dagger.producers.internal.AbstractProducesMethodProducer;",
|
|
"import dagger.producers.monitoring.ProducerToken;",
|
|
"import dagger.producers.monitoring.ProductionComponentMonitor;",
|
|
"import java.util.concurrent.Executor;",
|
|
"import javax.inject.Provider;"),
|
|
"",
|
|
GeneratedLines.generatedAnnotationsWithoutSuppressWarnings(),
|
|
"@SuppressWarnings({\"FutureReturnValueIgnored\", \"unchecked\", \"rawtypes\"})",
|
|
"public final class TestModule_ProduceStringFactory",
|
|
" extends AbstractProducesMethodProducer<Void, String> {",
|
|
" private final TestModule module;",
|
|
"",
|
|
" private TestModule_ProduceStringFactory(",
|
|
" TestModule module,",
|
|
" Provider<Executor> executorProvider,",
|
|
" Provider<ProductionComponentMonitor> productionComponentMonitorProvider) {",
|
|
" super(",
|
|
" productionComponentMonitorProvider,",
|
|
" ProducerToken.create(TestModule_ProduceStringFactory.class),",
|
|
" executorProvider);",
|
|
" this.module = module;",
|
|
" }",
|
|
"",
|
|
" public static TestModule_ProduceStringFactory create(",
|
|
" TestModule module,",
|
|
" Provider<Executor> executorProvider,",
|
|
" Provider<ProductionComponentMonitor> productionComponentMonitorProvider) {",
|
|
" return new TestModule_ProduceStringFactory(",
|
|
" module, executorProvider, productionComponentMonitorProvider);",
|
|
" }",
|
|
"",
|
|
" @Override protected ListenableFuture<Void> collectDependencies() {",
|
|
" return Futures.<Void>immediateFuture(null);",
|
|
" }",
|
|
"",
|
|
" @Override public ListenableFuture<String> callProducesMethod(Void ignoredVoidArg) {",
|
|
" return module.produceString();",
|
|
" }",
|
|
"}");
|
|
assertAbout(javaSource())
|
|
.that(moduleFile)
|
|
.processedWith(new ComponentProcessor())
|
|
.compilesWithoutError()
|
|
.and()
|
|
.generatesSources(factoryFile);
|
|
}
|
|
|
|
@Test
|
|
public void singleProducesMethodNoArgsFutureWithProducerName() {
|
|
JavaFileObject moduleFile =
|
|
JavaFileObjects.forSourceLines(
|
|
"test.TestModule",
|
|
"package test;",
|
|
"",
|
|
"import com.google.common.util.concurrent.Futures;",
|
|
"import com.google.common.util.concurrent.ListenableFuture;",
|
|
"import dagger.producers.ProducerModule;",
|
|
"import dagger.producers.Produces;",
|
|
"",
|
|
"@ProducerModule",
|
|
"final class TestModule {",
|
|
" @Produces ListenableFuture<String> produceString() {",
|
|
" return Futures.immediateFuture(\"\");",
|
|
" }",
|
|
"}");
|
|
JavaFileObject factoryFile =
|
|
JavaFileObjects.forSourceLines(
|
|
"TestModule_ProduceStringFactory",
|
|
"package test;",
|
|
"",
|
|
GeneratedLines.generatedImports(
|
|
"import com.google.common.util.concurrent.Futures;",
|
|
"import com.google.common.util.concurrent.ListenableFuture;",
|
|
"import dagger.producers.internal.AbstractProducesMethodProducer;",
|
|
"import dagger.producers.monitoring.ProducerToken;",
|
|
"import dagger.producers.monitoring.ProductionComponentMonitor;",
|
|
"import java.util.concurrent.Executor;",
|
|
"import javax.inject.Provider;"),
|
|
"",
|
|
GeneratedLines.generatedAnnotationsWithoutSuppressWarnings(),
|
|
"@SuppressWarnings({\"FutureReturnValueIgnored\", \"unchecked\", \"rawtypes\"})",
|
|
"public final class TestModule_ProduceStringFactory",
|
|
" extends AbstractProducesMethodProducer<Void, String> {",
|
|
" private final TestModule module;",
|
|
"",
|
|
" private TestModule_ProduceStringFactory(",
|
|
" TestModule module,",
|
|
" Provider<Executor> executorProvider,",
|
|
" Provider<ProductionComponentMonitor> productionComponentMonitorProvider) {",
|
|
" super(",
|
|
" productionComponentMonitorProvider,",
|
|
" ProducerToken.create(\"test.TestModule#produceString\"),",
|
|
" executorProvider);",
|
|
" this.module = module;",
|
|
" }",
|
|
"",
|
|
" public static TestModule_ProduceStringFactory create(",
|
|
" TestModule module,",
|
|
" Provider<Executor> executorProvider,",
|
|
" Provider<ProductionComponentMonitor> productionComponentMonitorProvider) {",
|
|
" return new TestModule_ProduceStringFactory(",
|
|
" module, executorProvider, productionComponentMonitorProvider);",
|
|
" }",
|
|
"",
|
|
" @Override protected ListenableFuture<Void> collectDependencies() {",
|
|
" return Futures.<Void>immediateFuture(null);",
|
|
" }",
|
|
"",
|
|
" @Override public ListenableFuture<String> callProducesMethod(Void ignoredVoidArg) {",
|
|
" return module.produceString();",
|
|
" }",
|
|
"}");
|
|
assertAbout(javaSource())
|
|
.that(moduleFile)
|
|
.withCompilerOptions("-Adagger.writeProducerNameInToken=ENABLED")
|
|
.processedWith(new ComponentProcessor())
|
|
.compilesWithoutError()
|
|
.and()
|
|
.generatesSources(factoryFile);
|
|
}
|
|
|
|
@Test
|
|
public void producesMethodMultipleQualifiersOnMethod() {
|
|
assertThatProductionModuleMethod(
|
|
"@Produces @QualifierA @QualifierB static String produceString() { return null; }")
|
|
.importing(ListenableFuture.class, QualifierA.class, QualifierB.class)
|
|
.hasError("may not use more than one @Qualifier");
|
|
}
|
|
|
|
@Test
|
|
public void producesMethodMultipleQualifiersOnParameter() {
|
|
assertThatProductionModuleMethod(
|
|
"@Produces static String produceString(@QualifierA @QualifierB Object input) "
|
|
+ "{ return null; }")
|
|
.importing(ListenableFuture.class, QualifierA.class, QualifierB.class)
|
|
.hasError("may not use more than one @Qualifier");
|
|
}
|
|
|
|
@Test
|
|
public void producesMethodWildcardDependency() {
|
|
assertThatProductionModuleMethod(
|
|
"@Produces static String produceString(Provider<? extends Number> numberProvider) "
|
|
+ "{ return null; }")
|
|
.importing(ListenableFuture.class, QualifierA.class, QualifierB.class)
|
|
.hasError(
|
|
"Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, or Produced<T> "
|
|
+ "when T is a wildcard type such as ? extends java.lang.Number");
|
|
}
|
|
|
|
@Qualifier
|
|
@Retention(RUNTIME)
|
|
public @interface QualifierA {}
|
|
|
|
@Qualifier
|
|
@Retention(RUNTIME)
|
|
public @interface QualifierB {}
|
|
}
|