/* * 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. */ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.Compilers.daggerCompiler; import static dagger.internal.codegen.TestUtils.message; import androidx.room.compiler.processing.util.Source; import com.google.common.collect.ImmutableMap; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; import dagger.testing.compile.CompilerTests; import javax.tools.JavaFileObject; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public final class ComponentValidationTest { @Test public void componentOnConcreteClass() { Source componentFile = CompilerTests.javaSource( "test.NotAComponent", "package test;", "", "import dagger.Component;", "", "@Component", "final class NotAComponent {}"); CompilerTests.daggerCompiler(componentFile) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining("interface"); }); } @Test public void componentOnOverridingBuilder_failsWhenMethodNameConflictsWithStaticCreatorName() { Source componentFile = CompilerTests.javaSource( "test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component(modules=TestModule.class)", "interface TestComponent {", " String builder();", "}"); Source moduleFile = CompilerTests.javaSource( "test.TestModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "interface TestModule {", " @Provides", " static String provideString() { return \"test\"; }", "}"); CompilerTests.daggerCompiler(componentFile, moduleFile) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( "The method test.TestComponent.builder() conflicts with a method"); }); } @Test public void componentOnOverridingCreate_failsWhenGeneratedCreateMethod() { Source componentFile = CompilerTests.javaSource( "test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component(modules=TestModule.class)", "interface TestComponent {", " String create();", "}"); Source moduleFile = CompilerTests.javaSource( "test.TestModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "interface TestModule {", " @Provides", " static String provideString() { return \"test\"; }", "}"); CompilerTests.daggerCompiler(componentFile, moduleFile) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( "The method test.TestComponent.create() conflicts with a method"); }); } @Test public void subcomponentMethodNameBuilder_succeeds() { Source componentFile = CompilerTests.javaSource( "test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component", "interface TestComponent {", " TestSubcomponent.Builder subcomponent();", "}"); Source subcomponentFile = CompilerTests.javaSource( "test.TestSubcomponent", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent(modules=TestModule.class)", "interface TestSubcomponent {", " String builder();", " @Subcomponent.Builder", " interface Builder {", " TestSubcomponent build();", " }", "}"); Source moduleFile = CompilerTests.javaSource( "test.TestModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "interface TestModule {", " @Provides", " static String provideString() { return \"test\"; }", "}"); CompilerTests.daggerCompiler(componentFile, subcomponentFile, moduleFile) .compile(subject -> subject.hasErrorCount(0)); } @Test public void componentOnEnum() { Source componentFile = CompilerTests.javaSource( "test.NotAComponent", "package test;", "", "import dagger.Component;", "", "@Component", "enum NotAComponent {", " INSTANCE", "}"); CompilerTests.daggerCompiler(componentFile) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining("interface"); }); } @Test public void componentOnAnnotation() { Source componentFile = CompilerTests.javaSource( "test.NotAComponent", "package test;", "", "import dagger.Component;", "", "@Component", "@interface NotAComponent {}"); CompilerTests.daggerCompiler(componentFile) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining("interface"); }); } @Test public void nonModuleModule() { Source componentFile = CompilerTests.javaSource( "test.NotAComponent", "package test;", "", "import dagger.Component;", "", "@Component(modules = Object.class)", "interface NotAComponent {}"); CompilerTests.daggerCompiler(componentFile) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining("is not annotated with @Module"); }); } @Test public void componentWithInvalidModule() { Source module = CompilerTests.javaSource( "test.BadModule", "package test;", "", "import dagger.Binds;", "import dagger.Module;", "", "@Module", "abstract class BadModule {", " @Binds abstract Object noParameters();", "}"); Source component = CompilerTests.javaSource( "test.BadComponent", "package test;", "", "import dagger.Component;", "", "@Component(modules = BadModule.class)", "interface BadComponent {", " Object object();", "}"); CompilerTests.daggerCompiler(module, component) .compile( subject -> { subject.hasErrorCount(2); subject.hasErrorContaining("test.BadModule has errors") .onSource(component) .onLine(5); subject.hasErrorContaining( "@Binds methods must have exactly one parameter, whose type is assignable to " + "the return type") .onSource(module) .onLine(8); }); } @Test public void attemptToInjectWildcardGenerics() { Source testComponent = CompilerTests.javaSource( "test.TestComponent", "package test;", "", "import dagger.Component;", "import dagger.Lazy;", "import javax.inject.Provider;", "", "@Component", "interface TestComponent {", " Lazy wildcardNumberLazy();", " Provider wildcardNumberProvider();", "}"); CompilerTests.daggerCompiler(testComponent) .compile( subject -> { subject.hasErrorCount(2); subject.hasErrorContaining("wildcard type").onSource(testComponent).onLine(9); subject.hasErrorContaining("wildcard type").onSource(testComponent).onLine(10); }); } // TODO(b/245954367): Migrate test to XProcessing Testing after this bug has been fixed. @Test public void invalidComponentDependencies() { JavaFileObject testComponent = JavaFileObjects.forSourceLines( "test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component(dependencies = int.class)", "interface TestComponent {}"); Compilation compilation = daggerCompiler().compile(testComponent); assertThat(compilation).failed(); assertThat(compilation).hadErrorContaining("int is not a valid component dependency type"); } // TODO(b/245954367): Migrate test to XProcessing Testing after this bug has been fixed. @Test public void invalidComponentModules() { JavaFileObject testComponent = JavaFileObjects.forSourceLines( "test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component(modules = int.class)", "interface TestComponent {}"); Compilation compilation = daggerCompiler().compile(testComponent); assertThat(compilation).failed(); assertThat(compilation).hadErrorContaining("int is not a valid module type"); } @Test public void moduleInDependencies() { Source testModule = CompilerTests.javaSource( "test.TestModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "final class TestModule {", " @Provides String s() { return null; }", "}"); Source testComponent = CompilerTests.javaSource( "test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component(dependencies = TestModule.class)", "interface TestComponent {}"); CompilerTests.daggerCompiler(testModule, testComponent) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( "test.TestModule is a module, which cannot be a component dependency"); }); } @Test public void componentDependencyMustNotCycle_Direct() { Source shortLifetime = CompilerTests.javaSource( "test.ComponentShort", "package test;", "", "import dagger.Component;", "", "@Component(dependencies = ComponentShort.class)", "interface ComponentShort {", "}"); String errorMessage = message( "test.ComponentShort contains a cycle in its component dependencies:", " test.ComponentShort"); CompilerTests.daggerCompiler(shortLifetime) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining(errorMessage); }); // Test that this also fails when transitive validation is disabled. CompilerTests.daggerCompiler(shortLifetime) .withProcessingOptions( ImmutableMap.of("dagger.validateTransitiveComponentDependencies", "DISABLED")) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining(errorMessage); }); } @Test public void componentDependencyMustNotCycle_Indirect() { Source longLifetime = CompilerTests.javaSource( "test.ComponentLong", "package test;", "", "import dagger.Component;", "", "@Component(dependencies = ComponentMedium.class)", "interface ComponentLong {", "}"); Source mediumLifetime = CompilerTests.javaSource( "test.ComponentMedium", "package test;", "", "import dagger.Component;", "", "@Component(dependencies = ComponentLong.class)", "interface ComponentMedium {", "}"); Source shortLifetime = CompilerTests.javaSource( "test.ComponentShort", "package test;", "", "import dagger.Component;", "", "@Component(dependencies = ComponentMedium.class)", "interface ComponentShort {", "}"); CompilerTests.daggerCompiler(longLifetime, mediumLifetime, shortLifetime) .compile( subject -> { subject.hasErrorCount(3); subject.hasErrorContaining( message( "test.ComponentLong contains a cycle in its component dependencies:", " test.ComponentLong", " test.ComponentMedium", " test.ComponentLong")) .onSource(longLifetime); subject.hasErrorContaining( message( "test.ComponentMedium contains a cycle in its component dependencies:", " test.ComponentMedium", " test.ComponentLong", " test.ComponentMedium")) .onSource(mediumLifetime); subject.hasErrorContaining( message( "test.ComponentShort contains a cycle in its component dependencies:", " test.ComponentMedium", " test.ComponentLong", " test.ComponentMedium", " test.ComponentShort")) .onSource(shortLifetime); }); // Test that compilation succeeds when transitive validation is disabled because the cycle // cannot be detected. CompilerTests.daggerCompiler(longLifetime, mediumLifetime, shortLifetime) .withProcessingOptions( ImmutableMap.of("dagger.validateTransitiveComponentDependencies", "DISABLED")) .compile(subject -> subject.hasErrorCount(0)); } @Test public void abstractModuleWithInstanceMethod() { Source module = CompilerTests.javaSource( "test.TestModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "abstract class TestModule {", " @Provides int i() { return 1; }", "}"); Source component = CompilerTests.javaSource( "test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component(modules = TestModule.class)", "interface TestComponent {", " int i();", "}"); CompilerTests.daggerCompiler(module, component) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( "TestModule is abstract and has instance @Provides methods") .onSource(component) .onLineContaining("interface TestComponent"); }); } @Test public void abstractModuleWithInstanceMethod_subclassedIsAllowed() { Source abstractModule = CompilerTests.javaSource( "test.AbstractModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "abstract class AbstractModule {", " @Provides int i() { return 1; }", "}"); Source subclassedModule = CompilerTests.javaSource( "test.SubclassedModule", "package test;", "", "import dagger.Module;", "", "@Module", "class SubclassedModule extends AbstractModule {}"); Source component = CompilerTests.javaSource( "test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component(modules = SubclassedModule.class)", "interface TestComponent {", " int i();", "}"); CompilerTests.daggerCompiler(abstractModule, subclassedModule, component) .compile(subject -> subject.hasErrorCount(0)); } }