/* * 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 dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatMethodInUnannotatedClass; import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatProductionModuleMethod; import static java.lang.annotation.RetentionPolicy.RUNTIME; import androidx.room.compiler.processing.util.Source; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.ListenableFuture; import dagger.testing.compile.CompilerTests; import dagger.testing.golden.GoldenFileRule; import java.lang.annotation.Retention; import javax.inject.Qualifier; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class ProducerModuleFactoryGeneratorTest { @Rule public GoldenFileRule goldenFileRule = new GoldenFileRule(); @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 produceProvider() {}") .hasError("@Produces methods must not return framework types"); } @Test public void producesLazy() { assertThatProductionModuleMethod("@Produces Lazy produceLazy() {}") .hasError("@Produces methods must not return framework types"); } @Test public void producesMembersInjector() { assertThatProductionModuleMethod( "@Produces MembersInjector produceMembersInjector() {}") .hasError("@Produces methods must not return framework types"); } @Test public void producesProducer() { assertThatProductionModuleMethod("@Produces Producer produceProducer() {}") .hasError("@Produces methods must not return framework types"); } @Test public void producesProduced() { assertThatProductionModuleMethod("@Produces Produced 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 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 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> 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 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> 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() { Source moduleFile = CompilerTests.javaSource( "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"; CompilerTests.daggerCompiler(moduleFile) .compile( subject -> { subject.hasErrorCount(2); subject.hasErrorContaining(errorMessage).onSource(moduleFile).onLine(8); subject.hasErrorContaining(errorMessage).onSource(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() { Source moduleFile = CompilerTests.javaSource("test.Enclosing", "package test;", "", "import dagger.producers.ProducerModule;", "", "final class Enclosing {", " @ProducerModule private static final class PrivateModule {", " }", "}"); CompilerTests.daggerCompiler(moduleFile) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining("Modules cannot be private") .onSource(moduleFile) .onLine(6); }); } @Test public void enclosedInPrivateModule() { Source moduleFile = CompilerTests.javaSource( "test.Enclosing", "package test;", "", "import dagger.producers.ProducerModule;", "", "final class Enclosing {", " private static final class PrivateEnclosing {", " @ProducerModule static final class TestModule {", " }", " }", "}"); CompilerTests.daggerCompiler(moduleFile) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining("Modules cannot be enclosed in private types") .onSource(moduleFile) .onLine(7); }); } @Test public void includesNonModule() { Source xFile = CompilerTests.javaSource( "test.X", "package test;", "", "public final class X {}"); Source moduleFile = CompilerTests.javaSource( "test.FooModule", "package test;", "", "import dagger.producers.ProducerModule;", "", "@ProducerModule(includes = X.class)", "public final class FooModule {", "}"); CompilerTests.daggerCompiler(xFile, moduleFile) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( "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() { Source publicModuleFile = CompilerTests.javaSource( "test.PublicModule", "package test;", "", "import dagger.producers.ProducerModule;", "", "@ProducerModule(includes = {", " BadNonPublicModule.class, OtherPublicModule.class, OkNonPublicModule.class", "})", "public final class PublicModule {}"); Source badNonPublicModuleFile = CompilerTests.javaSource( "test.BadNonPublicModule", "package test;", "", "import dagger.producers.ProducerModule;", "import dagger.producers.Produces;", "", "@ProducerModule", "final class BadNonPublicModule {", " @Produces", " int produceInt() {", " return 42;", " }", "}"); Source okNonPublicModuleFile = CompilerTests.javaSource( "test.OkNonPublicModule", "package test;", "", "import dagger.producers.ProducerModule;", "import dagger.producers.Produces;", "", "@ProducerModule", "final class OkNonPublicModule {", " @Produces", " static String produceString() {", " return \"foo\";", " }", "}"); Source otherPublicModuleFile = CompilerTests.javaSource( "test.OtherPublicModule", "package test;", "", "import dagger.producers.ProducerModule;", "", "@ProducerModule", "public final class OtherPublicModule {", "}"); CompilerTests.daggerCompiler( publicModuleFile, badNonPublicModuleFile, okNonPublicModuleFile, otherPublicModuleFile) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( "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.") .onSource(publicModuleFile) .onLine(8); }); } @Test public void argumentNamedModuleCompiles() { Source moduleFile = CompilerTests.javaSource( "test.TestModule", "package test;", "", "import dagger.producers.ProducerModule;", "import dagger.producers.Produces;", "", "@ProducerModule", "final class TestModule {", " @Produces String produceString(int module) {", " return null;", " }", "}"); CompilerTests.daggerCompiler(moduleFile) .compile(subject -> subject.hasErrorCount(0)); } @Test public void singleProducesMethodNoArgsFuture() { Source moduleFile = CompilerTests.javaSource( "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 produceString() {", " return null;", " }", "}"); CompilerTests.daggerCompiler(moduleFile) .compile( subject -> { subject.hasErrorCount(0); subject.generatedSource( goldenFileRule.goldenSource("test/TestModule_ProduceStringFactory")); }); } @Test public void singleProducesMethodNoArgsFutureWithProducerName() { Source moduleFile = CompilerTests.javaSource( "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 produceString() {", " return Futures.immediateFuture(\"\");", " }", "}"); CompilerTests.daggerCompiler(moduleFile) .withProcessingOptions(ImmutableMap.of("dagger.writeProducerNameInToken", "ENABLED")) .compile( subject -> { subject.hasErrorCount(0); subject.generatedSource( goldenFileRule.goldenSource("test/TestModule_ProduceStringFactory")); }); } @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 numberProvider) " + "{ return null; }") .importing(ListenableFuture.class, QualifierA.class, QualifierB.class) .hasError( "Dagger does not support injecting Provider, Lazy, Producer, or Produced " + "when T is a wildcard type such as ? extends java.lang.Number"); } @Qualifier @Retention(RUNTIME) public @interface QualifierA {} @Qualifier @Retention(RUNTIME) public @interface QualifierB {} }