| /* |
| * 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<Object[]> 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<String> stringSet() { return new HashSet<String>(); }", |
| " }", |
| "", |
| " @Module(includes = { TestModule1.class, TestModule2.class})", |
| " abstract static class TestModule3 {}", |
| "", |
| " @Component(modules = { TestModule1.class, TestModule2.class })", |
| " interface TestComponent {", |
| " Set<String> getStringSet();", |
| " }", |
| "}"); |
| |
| Compilation compilation = |
| compilerWithOptions( |
| fullBindingGraphValidationOption()) |
| .compile(component); |
| assertThat(compilation).failed(); |
| assertThat(compilation) |
| .hadErrorContaining( |
| message( |
| "Set<String> 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<String> 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<String, String> stringMap() {", |
| " return new HashMap<String, String>();", |
| " }", |
| " }", |
| "", |
| " @Module(includes = { TestModule1.class, TestModule2.class})", |
| " abstract static class TestModule3 {}", |
| "", |
| " @Component(modules = { TestModule1.class, TestModule2.class })", |
| " interface TestComponent {", |
| " Map<String, String> getStringMap();", |
| " }", |
| "}"); |
| |
| Compilation compilation = |
| compilerWithOptions( |
| fullBindingGraphValidationOption()) |
| .compile(component); |
| assertThat(compilation).failed(); |
| assertThat(compilation) |
| .hadErrorContaining( |
| message( |
| "Map<String,String> 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<String,String> 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<String> stringSet();", |
| " }", |
| "", |
| " @Module", |
| " static class TestModule2 {", |
| " @Provides Set<String> stringSet() { return new HashSet<String>(); }", |
| " }", |
| "", |
| " @Module(includes = { TestModule1.class, TestModule2.class})", |
| " abstract static class TestModule3 {}", |
| "", |
| " @Component(modules = { TestModule1.class, TestModule2.class })", |
| " interface TestComponent {", |
| " Set<String> getStringSet();", |
| " }", |
| "}"); |
| |
| Compilation compilation = |
| compilerWithOptions( |
| fullBindingGraphValidationOption()) |
| .compile(component); |
| assertThat(compilation).failed(); |
| assertThat(compilation) |
| .hadErrorContaining( |
| message( |
| "Set<String> has incompatible bindings or declarations:", |
| " Set bindings and declarations:", |
| " @Multibinds Set<String> " |
| + "Outer.TestModule1.stringSet()", |
| " Unique bindings and declarations:", |
| " @Provides Set<String> 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<String, String> stringMap();", |
| " }", |
| "", |
| " @Module", |
| " static class TestModule2 {", |
| " @Provides Map<String, String> stringMap() {", |
| " return new HashMap<String, String>();", |
| " }", |
| " }", |
| "", |
| " @Module(includes = { TestModule1.class, TestModule2.class})", |
| " abstract static class TestModule3 {}", |
| "", |
| " @Component(modules = { TestModule1.class, TestModule2.class })", |
| " interface TestComponent {", |
| " Map<String, String> getStringMap();", |
| " }", |
| "}"); |
| |
| Compilation compilation = |
| compilerWithOptions( |
| fullBindingGraphValidationOption()) |
| .compile(component); |
| assertThat(compilation).failed(); |
| assertThat(compilation) |
| .hadErrorContaining( |
| message( |
| "Map<String,String> has incompatible bindings " |
| + "or declarations:", |
| " Map bindings and declarations:", |
| " @Multibinds Map<String,String> " |
| + "Outer.TestModule1.stringMap()", |
| " Unique bindings and declarations:", |
| " @Provides Map<String,String> 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<String> 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<String> 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<String>) [Injected1]", |
| " @Provides Foo Provided1.Provided1Module.provideFoo(Set<String>) " |
| + "[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<Object> 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); |
| } |
| } |