blob: 3e416e993e9a381cc538637c2e9e650f2746959d [file] [log] [blame]
/*
* 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.compilerWithOptions;
import static dagger.internal.codegen.Compilers.daggerCompiler;
import com.google.testing.compile.Compilation;
import com.google.testing.compile.JavaFileObjects;
import javax.tools.JavaFileObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Producer-specific validation tests. */
@RunWith(JUnit4.class)
public class ProductionGraphValidationTest {
private static final JavaFileObject EXECUTOR_MODULE =
JavaFileObjects.forSourceLines(
"test.ExecutorModule",
"package test;",
"",
"import com.google.common.util.concurrent.MoreExecutors;",
"import dagger.Module;",
"import dagger.Provides;",
"import dagger.producers.Production;",
"import java.util.concurrent.Executor;",
"",
"@Module",
"class ExecutorModule {",
" @Provides @Production Executor executor() {",
" return MoreExecutors.directExecutor();",
" }",
"}");
@Test public void componentWithUnprovidedInput() {
JavaFileObject component = JavaFileObjects.forSourceLines("test.MyComponent",
"package test;",
"",
"import com.google.common.util.concurrent.ListenableFuture;",
"import dagger.producers.ProductionComponent;",
"",
"@ProductionComponent(modules = {ExecutorModule.class, FooModule.class})",
"interface MyComponent {",
" ListenableFuture<Foo> getFoo();",
"}");
JavaFileObject module = JavaFileObjects.forSourceLines("test.FooModule",
"package test;",
"",
"import dagger.producers.ProducerModule;",
"import dagger.producers.Produces;",
"",
"class Foo {}",
"class Bar {}",
"",
"@ProducerModule",
"class FooModule {",
" @Produces Foo foo(Bar bar) {",
" return null;",
" }",
"}");
Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, module, component);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
"Bar cannot be provided without an @Inject constructor or an @Provides- or "
+ "@Produces-annotated method.")
.inFile(component)
.onLineContaining("interface MyComponent");
}
@Test public void componentProductionWithNoDependencyChain() {
JavaFileObject component = JavaFileObjects.forSourceLines("test.TestClass",
"package test;",
"",
"import com.google.common.util.concurrent.ListenableFuture;",
"import dagger.producers.ProductionComponent;",
"",
"final class TestClass {",
" interface A {}",
"",
" @ProductionComponent(modules = ExecutorModule.class)",
" interface AComponent {",
" ListenableFuture<A> getA();",
" }",
"}");
Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, component);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
"TestClass.A cannot be provided without an @Provides- or @Produces-annotated "
+ "method.")
.inFile(component)
.onLineContaining("interface AComponent");
}
@Test public void provisionDependsOnProduction() {
JavaFileObject component =
JavaFileObjects.forSourceLines(
"test.TestClass",
"package test;",
"",
"import com.google.common.util.concurrent.ListenableFuture;",
"import dagger.Provides;",
"import dagger.producers.ProducerModule;",
"import dagger.producers.Produces;",
"import dagger.producers.ProductionComponent;",
"",
"final class TestClass {",
" interface A {}",
" interface B {}",
"",
" @ProducerModule(includes = BModule.class)",
" final class AModule {",
" @Provides A a(B b) {",
" return null;",
" }",
" }",
"",
" @ProducerModule",
" final class BModule {",
" @Produces ListenableFuture<B> b() {",
" return null;",
" }",
" }",
"",
" @ProductionComponent(modules = {ExecutorModule.class, AModule.class})",
" interface AComponent {",
" ListenableFuture<A> getA();",
" }",
"}");
Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, component);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining("TestClass.A is a provision, which cannot depend on a production.")
.inFile(component)
.onLineContaining("interface AComponent");
compilation =
compilerWithOptions("-Adagger.fullBindingGraphValidation=ERROR")
.compile(EXECUTOR_MODULE, component);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining("TestClass.A is a provision, which cannot depend on a production.")
.inFile(component)
.onLineContaining("class AModule");
}
@Test public void provisionEntryPointDependsOnProduction() {
JavaFileObject component =
JavaFileObjects.forSourceLines(
"test.TestClass",
"package test;",
"",
"import com.google.common.util.concurrent.ListenableFuture;",
"import dagger.producers.ProducerModule;",
"import dagger.producers.Produces;",
"import dagger.producers.ProductionComponent;",
"",
"final class TestClass {",
" interface A {}",
"",
" @ProducerModule",
" static final class AModule {",
" @Produces ListenableFuture<A> a() {",
" return null;",
" }",
" }",
"",
" @ProductionComponent(modules = {ExecutorModule.class, AModule.class})",
" interface AComponent {",
" A getA();",
" }",
"}");
Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, component);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
"TestClass.A is a provision entry-point, which cannot depend on a production.")
.inFile(component)
.onLineContaining("interface AComponent");
}
@Test
public void providingMultibindingWithProductions() {
JavaFileObject component =
JavaFileObjects.forSourceLines(
"test.TestClass",
"package test;",
"",
"import com.google.common.util.concurrent.ListenableFuture;",
"import dagger.Module;",
"import dagger.Provides;",
"import dagger.multibindings.IntoMap;",
"import dagger.multibindings.StringKey;",
"import dagger.producers.ProducerModule;",
"import dagger.producers.Produces;",
"import dagger.producers.ProductionComponent;",
"import java.util.Map;",
"import javax.inject.Provider;",
"",
"final class TestClass {",
" interface A {}",
" interface B {}",
"",
" @Module",
" static final class AModule {",
" @Provides static A a(Map<String, Provider<Object>> map) {",
" return null;",
" }",
"",
" @Provides @IntoMap @StringKey(\"a\") static Object aEntry() {",
" return \"a\";",
" }",
" }",
"",
" @ProducerModule",
" static final class BModule {",
" @Produces static B b(A a) {",
" return null;",
" }",
"",
" @Produces @IntoMap @StringKey(\"b\") static Object bEntry() {",
" return \"b\";",
" }",
" }",
"",
" @ProductionComponent(",
" modules = {ExecutorModule.class, AModule.class, BModule.class})",
" interface AComponent {",
" ListenableFuture<B> b();",
" }",
"}");
Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, component);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining("TestClass.A is a provision, which cannot depend on a production")
.inFile(component)
.onLineContaining("interface AComponent");
}
@Test
public void monitoringDependsOnUnboundType() {
JavaFileObject component =
JavaFileObjects.forSourceLines(
"test.TestClass",
"package test;",
"",
"import com.google.common.util.concurrent.ListenableFuture;",
"import dagger.Module;",
"import dagger.Provides;",
"import dagger.multibindings.IntoSet;",
"import dagger.producers.ProducerModule;",
"import dagger.producers.Produces;",
"import dagger.producers.ProductionComponent;",
"import dagger.producers.monitoring.ProductionComponentMonitor;",
"",
"final class TestClass {",
" interface A {}",
"",
" @Module",
" final class MonitoringModule {",
" @Provides @IntoSet",
" ProductionComponentMonitor.Factory monitorFactory(A unbound) {",
" return null;",
" }",
" }",
"",
" @ProducerModule",
" final class StringModule {",
" @Produces ListenableFuture<String> str() {",
" return null;",
" }",
" }",
"",
" @ProductionComponent(",
" modules = {ExecutorModule.class, MonitoringModule.class, StringModule.class}",
" )",
" interface StringComponent {",
" ListenableFuture<String> getString();",
" }",
"}");
Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, component);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
"TestClass.A cannot be provided without an @Provides-annotated method.")
.inFile(component)
.onLineContaining("interface StringComponent");
}
@Test
public void monitoringDependsOnProduction() {
JavaFileObject component =
JavaFileObjects.forSourceLines(
"test.TestClass",
"package test;",
"",
"import com.google.common.util.concurrent.ListenableFuture;",
"import dagger.Module;",
"import dagger.Provides;",
"import dagger.multibindings.IntoSet;",
"import dagger.producers.ProducerModule;",
"import dagger.producers.Produces;",
"import dagger.producers.ProductionComponent;",
"import dagger.producers.monitoring.ProductionComponentMonitor;",
"",
"final class TestClass {",
" interface A {}",
"",
" @Module",
" final class MonitoringModule {",
" @Provides @IntoSet ProductionComponentMonitor.Factory monitorFactory(A a) {",
" return null;",
" }",
" }",
"",
" @ProducerModule",
" final class StringModule {",
" @Produces A a() {",
" return null;",
" }",
"",
" @Produces ListenableFuture<String> str() {",
" return null;",
" }",
" }",
"",
" @ProductionComponent(",
" modules = {ExecutorModule.class, MonitoringModule.class, StringModule.class}",
" )",
" interface StringComponent {",
" ListenableFuture<String> getString();",
" }",
"}");
Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, component);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining(
"Set<ProductionComponentMonitor.Factory>"
+ " TestClass.MonitoringModule#monitorFactory is a provision,"
+ " which cannot depend on a production.")
.inFile(component)
.onLineContaining("interface StringComponent");
}
@Test
public void cycleNotBrokenByMap() {
JavaFileObject component =
JavaFileObjects.forSourceLines(
"test.TestComponent",
"package test;",
"",
"import com.google.common.util.concurrent.ListenableFuture;",
"import dagger.producers.ProductionComponent;",
"",
"@ProductionComponent(modules = {ExecutorModule.class, TestModule.class})",
"interface TestComponent {",
" ListenableFuture<String> string();",
"}");
JavaFileObject module =
JavaFileObjects.forSourceLines(
"test.TestModule",
"package test;",
"",
"import dagger.producers.ProducerModule;",
"import dagger.producers.Produces;",
"import dagger.multibindings.IntoMap;",
"import dagger.multibindings.StringKey;",
"import java.util.Map;",
"",
"@ProducerModule",
"final class TestModule {",
" @Produces static String string(Map<String, String> map) {",
" return \"string\";",
" }",
"",
" @Produces @IntoMap @StringKey(\"key\")",
" static String entry(String string) {",
" return string;",
" }",
"}");
Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, component, module);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining("cycle")
.inFile(component)
.onLineContaining("interface TestComponent");
}
@Test
public void cycleNotBrokenByProducerMap() {
JavaFileObject component =
JavaFileObjects.forSourceLines(
"test.TestComponent",
"package test;",
"",
"import com.google.common.util.concurrent.ListenableFuture;",
"import dagger.producers.ProductionComponent;",
"",
"@ProductionComponent(modules = {ExecutorModule.class, TestModule.class})",
"interface TestComponent {",
" ListenableFuture<String> string();",
"}");
JavaFileObject module =
JavaFileObjects.forSourceLines(
"test.TestModule",
"package test;",
"",
"import dagger.producers.Producer;",
"import dagger.producers.ProducerModule;",
"import dagger.producers.Produces;",
"import dagger.multibindings.StringKey;",
"import dagger.multibindings.IntoMap;",
"import java.util.Map;",
"",
"@ProducerModule",
"final class TestModule {",
" @Produces static String string(Map<String, Producer<String>> map) {",
" return \"string\";",
" }",
"",
" @Produces @IntoMap @StringKey(\"key\")",
" static String entry(String string) {",
" return string;",
" }",
"}");
Compilation compilation = daggerCompiler().compile(EXECUTOR_MODULE, component, module);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining("cycle")
.inFile(component)
.onLineContaining("interface TestComponent");
}
@Test
public void componentWithBadModule() {
JavaFileObject badModule =
JavaFileObjects.forSourceLines(
"test.BadModule",
"package test;",
"",
"import dagger.BindsOptionalOf;",
"import dagger.multibindings.Multibinds;",
"import dagger.Module;",
"import java.util.Set;",
"",
"@Module",
"abstract class BadModule {",
" @Multibinds",
" @BindsOptionalOf",
" abstract Set<String> strings();",
"}");
JavaFileObject badComponent =
JavaFileObjects.forSourceLines(
"test.BadComponent",
"package test;",
"",
"import dagger.Component;",
"import java.util.Optional;",
"import java.util.Set;",
"",
"@Component(modules = BadModule.class)",
"interface BadComponent {",
" Set<String> strings();",
" Optional<Set<String>> optionalStrings();",
"}");
Compilation compilation = daggerCompiler().compile(badModule, badComponent);
assertThat(compilation).failed();
assertThat(compilation)
.hadErrorContaining("BadModule has errors")
.inFile(badComponent)
.onLine(7);
}
}