/* * Copyright (C) 2018 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 static dagger.internal.codegen.TestUtils.message; import static org.junit.Assume.assumeFalse; import com.google.common.collect.ImmutableList; 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.Parameterized; import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) public class DuplicateBindingsValidationTest { @Parameters(name = "fullBindingGraphValidation={0}") public static ImmutableList parameters() { return ImmutableList.copyOf(new Object[][] {{false}, {true}}); } private final boolean fullBindingGraphValidation; public DuplicateBindingsValidationTest(boolean fullBindingGraphValidation) { this.fullBindingGraphValidation = fullBindingGraphValidation; } @Test public void duplicateExplicitBindings_ProvidesAndComponentProvision() { assumeFalse(fullBindingGraphValidation); JavaFileObject component = JavaFileObjects.forSourceLines("test.Outer", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "", "final class Outer {", " interface A {}", "", " interface B {}", "", " @Module", " static class AModule {", " @Provides String provideString() { return \"\"; }", " @Provides A provideA(String s) { return new A() {}; }", " }", "", " @Component(modules = AModule.class)", " interface Parent {", " A getA();", " }", "", " @Module", " static class BModule {", " @Provides B provideB(A a) { return new B() {}; }", " }", "", " @Component(dependencies = Parent.class, modules = { BModule.class, AModule.class})", " interface Child {", " B getB();", " }", "}"); Compilation compilation = compilerWithOptions( fullBindingGraphValidationOption()) .compile(component); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( message( "Outer.A is bound multiple times:", " @Provides Outer.A Outer.AModule.provideA(String)", " Outer.A Outer.Parent.getA()")) .inFile(component) .onLineContaining("interface Child"); } @Test public void duplicateExplicitBindings_TwoProvidesMethods() { JavaFileObject component = JavaFileObjects.forSourceLines( "test.Outer", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import javax.inject.Inject;", "", "final class Outer {", " interface A {}", "", " static class B {", " @Inject B(A a) {}", " }", "", " @Module", " static class Module1 {", " @Provides A provideA1() { return new A() {}; }", " }", "", " @Module", " static class Module2 {", " @Provides String provideString() { return \"\"; }", " @Provides A provideA2(String s) { return new A() {}; }", " }", "", " @Module(includes = { Module1.class, Module2.class})", " abstract static class Module3 {}", "", " @Component(modules = { Module1.class, Module2.class})", " interface TestComponent {", " A getA();", " B getB();", " }", "}"); Compilation compilation = compilerWithOptions( fullBindingGraphValidationOption()) .compile(component); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( message( "Outer.A is bound multiple times:", " @Provides Outer.A Outer.Module1.provideA1()", " @Provides Outer.A Outer.Module2.provideA2(String)")) .inFile(component) .onLineContaining("interface TestComponent"); if (fullBindingGraphValidation) { assertThat(compilation) .hadErrorContaining( message( "Outer.A is bound multiple times:", " @Provides Outer.A Outer.Module1.provideA1()", " @Provides Outer.A Outer.Module2.provideA2(String)")) .inFile(component) .onLineContaining("class Module3"); } // The duplicate bindngs are also requested from B, but we don't want to report them again. assertThat(compilation).hadErrorCount(fullBindingGraphValidation ? 2 : 1); } @Test public void duplicateExplicitBindings_ProvidesVsBinds() { JavaFileObject component = JavaFileObjects.forSourceLines( "test.Outer", "package test;", "", "import dagger.Binds;", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import javax.inject.Inject;", "", "final class Outer {", " interface A {}", "", " static final class B implements A {", " @Inject B() {}", " }", "", " @Module", " static class Module1 {", " @Provides A provideA1() { return new A() {}; }", " }", "", " @Module", " static abstract class Module2 {", " @Binds abstract A bindA2(B b);", " }", "", " @Module(includes = { Module1.class, Module2.class})", " abstract static class Module3 {}", "", " @Component(modules = { Module1.class, Module2.class})", " interface TestComponent {", " A getA();", " }", "}"); Compilation compilation = compilerWithOptions( fullBindingGraphValidationOption()) .compile(component); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( message( "Outer.A is bound multiple times:", " @Provides Outer.A Outer.Module1.provideA1()", " @Binds Outer.A Outer.Module2.bindA2(Outer.B)")) .inFile(component) .onLineContaining("interface TestComponent"); if (fullBindingGraphValidation) { assertThat(compilation) .hadErrorContaining( message( "Outer.A is bound multiple times:", " @Provides Outer.A Outer.Module1.provideA1()", " @Binds Outer.A Outer.Module2.bindA2(Outer.B)")) .inFile(component) .onLineContaining("class Module3"); } } @Test public void duplicateExplicitBindings_multibindingsAndExplicitSets() { JavaFileObject component = JavaFileObjects.forSourceLines( "test.Outer", "package test;", "", "import dagger.Binds;", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import dagger.multibindings.IntoSet;", "import java.util.HashSet;", "import java.util.Set;", "import javax.inject.Qualifier;", "", "final class Outer {", " @Qualifier @interface SomeQualifier {}", "", " @Module", " abstract static class TestModule1 {", " @Provides @IntoSet static String stringSetElement() { return \"\"; }", "", " @Binds", " @IntoSet abstract String bindStringSetElement(@SomeQualifier String value);", "", " @Provides @SomeQualifier", " static String provideSomeQualifiedString() { return \"\"; }", " }", "", " @Module", " static class TestModule2 {", " @Provides Set stringSet() { return new HashSet(); }", " }", "", " @Module(includes = { TestModule1.class, TestModule2.class})", " abstract static class TestModule3 {}", "", " @Component(modules = { TestModule1.class, TestModule2.class })", " interface TestComponent {", " Set getStringSet();", " }", "}"); Compilation compilation = compilerWithOptions( fullBindingGraphValidationOption()) .compile(component); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( message( "Set has incompatible bindings or declarations:", " Set bindings and declarations:", " @Binds @IntoSet String " + "Outer.TestModule1.bindStringSetElement(@Outer.SomeQualifier " + "String)", " @Provides @IntoSet String " + "Outer.TestModule1.stringSetElement()", " Unique bindings and declarations:", " @Provides Set Outer.TestModule2.stringSet()")) .inFile(component) .onLineContaining( fullBindingGraphValidation ? "class TestModule3" : "interface TestComponent"); } @Test public void duplicateExplicitBindings_multibindingsAndExplicitMaps() { JavaFileObject component = JavaFileObjects.forSourceLines( "test.Outer", "package test;", "", "import dagger.Binds;", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import dagger.multibindings.IntoMap;", "import dagger.multibindings.StringKey;", "import java.util.HashMap;", "import java.util.Map;", "import javax.inject.Qualifier;", "", "final class Outer {", " @Qualifier @interface SomeQualifier {}", "", " @Module", " abstract static class TestModule1 {", " @Provides @IntoMap", " @StringKey(\"foo\")", " static String stringMapEntry() { return \"\"; }", "", " @Binds @IntoMap @StringKey(\"bar\")", " abstract String bindStringMapEntry(@SomeQualifier String value);", "", " @Provides @SomeQualifier", " static String provideSomeQualifiedString() { return \"\"; }", " }", "", " @Module", " static class TestModule2 {", " @Provides Map stringMap() {", " return new HashMap();", " }", " }", "", " @Module(includes = { TestModule1.class, TestModule2.class})", " abstract static class TestModule3 {}", "", " @Component(modules = { TestModule1.class, TestModule2.class })", " interface TestComponent {", " Map getStringMap();", " }", "}"); Compilation compilation = compilerWithOptions( fullBindingGraphValidationOption()) .compile(component); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( message( "Map has incompatible bindings " + "or declarations:", " Map bindings and declarations:", " @Binds @IntoMap @StringKey(\"bar\") String" + " Outer.TestModule1.bindStringMapEntry(@Outer.SomeQualifier " + "String)", " @Provides @IntoMap @StringKey(\"foo\") String" + " Outer.TestModule1.stringMapEntry()", " Unique bindings and declarations:", " @Provides Map Outer.TestModule2.stringMap()")) .inFile(component) .onLineContaining( fullBindingGraphValidation ? "class TestModule3" : "interface TestComponent"); } @Test public void duplicateExplicitBindings_UniqueBindingAndMultibindingDeclaration_Set() { JavaFileObject component = JavaFileObjects.forSourceLines( "test.Outer", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import dagger.multibindings.Multibinds;", "import java.util.HashSet;", "import java.util.Set;", "", "final class Outer {", " @Module", " abstract static class TestModule1 {", " @Multibinds abstract Set stringSet();", " }", "", " @Module", " static class TestModule2 {", " @Provides Set stringSet() { return new HashSet(); }", " }", "", " @Module(includes = { TestModule1.class, TestModule2.class})", " abstract static class TestModule3 {}", "", " @Component(modules = { TestModule1.class, TestModule2.class })", " interface TestComponent {", " Set getStringSet();", " }", "}"); Compilation compilation = compilerWithOptions( fullBindingGraphValidationOption()) .compile(component); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( message( "Set has incompatible bindings or declarations:", " Set bindings and declarations:", " @Multibinds Set " + "Outer.TestModule1.stringSet()", " Unique bindings and declarations:", " @Provides Set Outer.TestModule2.stringSet()")) .inFile(component) .onLineContaining( fullBindingGraphValidation ? "class TestModule3" : "interface TestComponent"); } @Test public void duplicateExplicitBindings_UniqueBindingAndMultibindingDeclaration_Map() { JavaFileObject component = JavaFileObjects.forSourceLines( "test.Outer", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import dagger.multibindings.Multibinds;", "import java.util.HashMap;", "import java.util.Map;", "", "final class Outer {", " @Module", " abstract static class TestModule1 {", " @Multibinds abstract Map stringMap();", " }", "", " @Module", " static class TestModule2 {", " @Provides Map stringMap() {", " return new HashMap();", " }", " }", "", " @Module(includes = { TestModule1.class, TestModule2.class})", " abstract static class TestModule3 {}", "", " @Component(modules = { TestModule1.class, TestModule2.class })", " interface TestComponent {", " Map getStringMap();", " }", "}"); Compilation compilation = compilerWithOptions( fullBindingGraphValidationOption()) .compile(component); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( message( "Map has incompatible bindings " + "or declarations:", " Map bindings and declarations:", " @Multibinds Map " + "Outer.TestModule1.stringMap()", " Unique bindings and declarations:", " @Provides Map Outer.TestModule2.stringMap()")) .inFile(component) .onLineContaining( fullBindingGraphValidation ? "class TestModule3" : "interface TestComponent"); } @Test public void duplicateBindings_TruncateAfterLimit() { JavaFileObject component = JavaFileObjects.forSourceLines( "test.Outer", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import javax.inject.Inject;", "", "final class Outer {", " interface A {}", "", " @Module", " static class Module01 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module02 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module03 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module04 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module05 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module06 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module07 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module08 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module09 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module10 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module11 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module", " static class Module12 {", " @Provides A provideA() { return new A() {}; }", " }", "", " @Module(includes = {", " Module01.class,", " Module02.class,", " Module03.class,", " Module04.class,", " Module05.class,", " Module06.class,", " Module07.class,", " Module08.class,", " Module09.class,", " Module10.class,", " Module11.class,", " Module12.class", " })", " abstract static class Modules {}", "", " @Component(modules = {", " Module01.class,", " Module02.class,", " Module03.class,", " Module04.class,", " Module05.class,", " Module06.class,", " Module07.class,", " Module08.class,", " Module09.class,", " Module10.class,", " Module11.class,", " Module12.class", " })", " interface TestComponent {", " A getA();", " }", "}"); Compilation compilation = compilerWithOptions( fullBindingGraphValidationOption()) .compile(component); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( message( "Outer.A is bound multiple times:", " @Provides Outer.A Outer.Module01.provideA()", " @Provides Outer.A Outer.Module02.provideA()", " @Provides Outer.A Outer.Module03.provideA()", " @Provides Outer.A Outer.Module04.provideA()", " @Provides Outer.A Outer.Module05.provideA()", " @Provides Outer.A Outer.Module06.provideA()", " @Provides Outer.A Outer.Module07.provideA()", " @Provides Outer.A Outer.Module08.provideA()", " @Provides Outer.A Outer.Module09.provideA()", " @Provides Outer.A Outer.Module10.provideA()", " and 2 others")) .inFile(component) .onLineContaining(fullBindingGraphValidation ? "class Modules" : "interface TestComponent"); } @Test public void childBindingConflictsWithParent() { JavaFileObject aComponent = JavaFileObjects.forSourceLines( "test.A", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "", "@Component(modules = A.AModule.class)", "interface A {", " Object conflict();", "", " B.Builder b();", "", " @Module(subcomponents = B.class)", " static class AModule {", " @Provides static Object abConflict() {", " return \"a\";", " }", " }", "}"); JavaFileObject bComponent = JavaFileObjects.forSourceLines( "test.B", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.Subcomponent;", "", "@Subcomponent(modules = B.BModule.class)", "interface B {", " Object conflict();", "", " @Subcomponent.Builder", " interface Builder {", " B build();", " }", "", " @Module", " static class BModule {", " @Provides static Object abConflict() {", " return \"b\";", " }", " }", "}"); Compilation compilation = compilerWithOptions( fullBindingGraphValidationOption()) .compile(aComponent, bComponent); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( message( "Object is bound multiple times:", " @Provides Object test.A.AModule.abConflict()", " @Provides Object test.B.BModule.abConflict()")) .inFile(aComponent) .onLineContaining(fullBindingGraphValidation ? "class AModule" : "interface A {"); } @Test public void grandchildBindingConflictsWithGrandparent() { JavaFileObject aComponent = JavaFileObjects.forSourceLines( "test.A", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "", "@Component(modules = A.AModule.class)", "interface A {", " Object conflict();", "", " B.Builder b();", "", " @Module(subcomponents = B.class)", " static class AModule {", " @Provides static Object acConflict() {", " return \"a\";", " }", " }", "}"); JavaFileObject bComponent = JavaFileObjects.forSourceLines( "test.B", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent", "interface B {", " C.Builder c();", "", " @Subcomponent.Builder", " interface Builder {", " B build();", " }", "}"); JavaFileObject cComponent = JavaFileObjects.forSourceLines( "test.C", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.Subcomponent;", "", "@Subcomponent(modules = C.CModule.class)", "interface C {", " Object conflict();", "", " @Subcomponent.Builder", " interface Builder {", " C build();", " }", "", " @Module", " static class CModule {", " @Provides static Object acConflict() {", " return \"c\";", " }", " }", "}"); Compilation compilation = compilerWithOptions( fullBindingGraphValidationOption()) .compile(aComponent, bComponent, cComponent); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( message( "Object is bound multiple times:", " @Provides Object test.A.AModule.acConflict()", " @Provides Object test.C.CModule.acConflict()")) .inFile(aComponent) .onLineContaining(fullBindingGraphValidation ? "class AModule" : "interface A {"); } @Test public void grandchildBindingConflictsWithChild() { JavaFileObject aComponent = JavaFileObjects.forSourceLines( "test.A", "package test;", "", "import dagger.Component;", "", "@Component", "interface A {", " B b();", "}"); JavaFileObject bComponent = JavaFileObjects.forSourceLines( "test.B", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.Subcomponent;", "", "@Subcomponent(modules = B.BModule.class)", "interface B {", " Object conflict();", "", " C.Builder c();", "", " @Module(subcomponents = C.class)", " static class BModule {", " @Provides static Object bcConflict() {", " return \"b\";", " }", " }", "}"); JavaFileObject cComponent = JavaFileObjects.forSourceLines( "test.C", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.Subcomponent;", "", "@Subcomponent(modules = C.CModule.class)", "interface C {", " Object conflict();", "", " @Subcomponent.Builder", " interface Builder {", " C build();", " }", "", " @Module", " static class CModule {", " @Provides static Object bcConflict() {", " return \"c\";", " }", " }", "}"); Compilation compilation = compilerWithOptions( fullBindingGraphValidationOption()) .compile(aComponent, bComponent, cComponent); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( message( "Object is bound multiple times:", " @Provides Object test.B.BModule.bcConflict()", " @Provides Object test.C.CModule.bcConflict()")) .inFile(fullBindingGraphValidation ? bComponent : aComponent) .onLineContaining(fullBindingGraphValidation ? "class BModule" : "interface A {"); } @Test public void childProvidesConflictsWithParentInjects() { assumeFalse(fullBindingGraphValidation); JavaFileObject foo = JavaFileObjects.forSourceLines( "test.Foo", "package test;", "", "import java.util.Set;", "import javax.inject.Inject;", "", "final class Foo {", " @Inject Foo(Set strings) {}", "}"); JavaFileObject injected1 = JavaFileObjects.forSourceLines( "test.Injected1", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import dagger.multibindings.IntoSet;", "import java.util.Set;", "", "@Component(modules = Injected1.Injected1Module.class)", "interface Injected1 {", " Foo foo();", " Injected2 injected2();", "", " @Module", " interface Injected1Module {", " @Provides @IntoSet static String string() {", " return \"injected1\";", " }", " }", "}"); JavaFileObject injected2 = JavaFileObjects.forSourceLines( "test.Injected2", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.Subcomponent;", "import dagger.multibindings.IntoSet;", "import java.util.Set;", "", "@Subcomponent(modules = Injected2.Injected2Module.class)", "interface Injected2 {", " Foo foo();", " Provided1 provided1();", "", " @Module", " interface Injected2Module {", " @Provides @IntoSet static String string() {", " return \"injected2\";", " }", " }", "}"); JavaFileObject provided1 = JavaFileObjects.forSourceLines( "test.Provided1", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.Subcomponent;", "import dagger.multibindings.IntoSet;", "import java.util.Set;", "", "@Subcomponent(modules = Provided1.Provided1Module.class)", "interface Provided1 {", " Foo foo();", " Provided2 provided2();", "", " @Module", " static class Provided1Module {", " @Provides static Foo provideFoo(Set strings) {", " return new Foo(strings);", " }", "", " @Provides @IntoSet static String string() {", " return \"provided1\";", " }", " }", "}"); JavaFileObject provided2 = JavaFileObjects.forSourceLines( "test.Provided2", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.Subcomponent;", "import dagger.multibindings.IntoSet;", "", "@Subcomponent(modules = Provided2.Provided2Module.class)", "interface Provided2 {", " Foo foo();", "", " @Module", " static class Provided2Module {", " @Provides @IntoSet static String string() {", " return \"provided2\";", " }", " }", "}"); Compilation compilation = daggerCompiler().compile(foo, injected1, injected2, provided1, provided2); assertThat(compilation).succeeded(); assertThat(compilation) .hadWarningContaining( message( "Foo is bound multiple times:", " @Inject Foo(Set) [Injected1]", " @Provides Foo Provided1.Provided1Module.provideFoo(Set) " + "[Injected1 → Injected2 → Provided1]")) .inFile(injected1) .onLineContaining("interface Injected1 {"); } @Test public void grandchildBindingConflictsWithParentWithNullableViolationAsWarning() { JavaFileObject parentConflictsWithChild = JavaFileObjects.forSourceLines( "test.ParentConflictsWithChild", "package test;", "", "import dagger.Component;", "import dagger.Module;", "import dagger.Provides;", "import javax.annotation.Nullable;", "", "@Component(modules = ParentConflictsWithChild.ParentModule.class)", "interface ParentConflictsWithChild {", " Child.Builder child();", "", " @Module(subcomponents = Child.class)", " static class ParentModule {", " @Provides @Nullable static Object nullableParentChildConflict() {", " return \"parent\";", " }", " }", "}"); JavaFileObject child = JavaFileObjects.forSourceLines( "test.Child", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.Subcomponent;", "", "@Subcomponent(modules = Child.ChildModule.class)", "interface Child {", " Object parentChildConflictThatViolatesNullability();", "", " @Subcomponent.Builder", " interface Builder {", " Child build();", " }", "", " @Module", " static class ChildModule {", " @Provides static Object nonNullableParentChildConflict() {", " return \"child\";", " }", " }", "}"); Compilation compilation = compilerWithOptions( "-Adagger.nullableValidation=WARNING", fullBindingGraphValidationOption()) .compile(parentConflictsWithChild, child); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( message( "Object is bound multiple times:", " @Provides Object Child.ChildModule.nonNullableParentChildConflict()", " @Provides @Nullable Object" + " ParentConflictsWithChild.ParentModule.nullableParentChildConflict()")) .inFile(parentConflictsWithChild) .onLineContaining( fullBindingGraphValidation ? "class ParentModule" : "interface ParentConflictsWithChild"); } private String fullBindingGraphValidationOption() { return "-Adagger.fullBindingGraphValidation=" + (fullBindingGraphValidation ? "ERROR" : "NONE"); } @Test public void reportedInParentAndChild() { JavaFileObject parent = JavaFileObjects.forSourceLines( "test.Parent", "package test;", "", "import dagger.Component;", "", "@Component(modules = ParentModule.class)", "interface Parent {", " Child.Builder childBuilder();", " String duplicated();", "}"); JavaFileObject parentModule = JavaFileObjects.forSourceLines( "test.ParentModule", "package test;", "", "import dagger.BindsOptionalOf;", "import dagger.Module;", "import dagger.Provides;", "import java.util.Optional;", "", "@Module", "interface ParentModule {", " @Provides static String one(Optional optional) { return \"one\"; }", " @Provides static String two() { return \"two\"; }", " @BindsOptionalOf Object optional();", "}"); JavaFileObject child = JavaFileObjects.forSourceLines( "test.Child", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent(modules = ChildModule.class)", "interface Child {", " String duplicated();", "", " @Subcomponent.Builder", " interface Builder {", " Child build();", " }", "}"); JavaFileObject childModule = JavaFileObjects.forSourceLines( "test.ChildModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import java.util.Optional;", "", "@Module", "interface ChildModule {", " @Provides static Object object() { return \"object\"; }", "}"); Compilation compilation = daggerCompiler().compile(parent, parentModule, child, childModule); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining("String is bound multiple times") .inFile(parent) .onLineContaining("interface Parent"); assertThat(compilation).hadErrorCount(1); } }