/* * 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 dagger.internal.codegen.TestUtils.endsWithMessage; import androidx.room.compiler.processing.util.Source; import com.google.common.collect.ImmutableMap; import dagger.testing.compile.CompilerTests; import java.util.regex.Pattern; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public final class FullBindingGraphValidationTest { private static final Source MODULE_WITH_ERRORS = CompilerTests.javaSource( "test.ModuleWithErrors", "package test;", "", "import dagger.Binds;", "import dagger.Module;", "", "@Module", "interface ModuleWithErrors {", " @Binds Object object1(String string);", " @Binds Object object2(Long l);", " @Binds Number missingDependency(Integer i);", "}"); // Make sure the error doesn't show other bindings or a dependency trace afterwards. private static final Pattern MODULE_WITH_ERRORS_MESSAGE = endsWithMessage( "\033[1;31m[Dagger/DuplicateBindings]\033[0m Object is bound multiple times:", " @Binds Object ModuleWithErrors.object1(String)", " @Binds Object ModuleWithErrors.object2(Long)", " in component: [ModuleWithErrors]", "", "======================", "Full classname legend:", "======================", "ModuleWithErrors: test.ModuleWithErrors", "========================", "End of classname legend:", "========================"); private static final Pattern INCLUDES_MODULE_WITH_ERRORS_MESSAGE = endsWithMessage( "\033[1;31m[Dagger/DuplicateBindings]\033[0m Object is bound multiple times:", " @Binds Object ModuleWithErrors.object1(String)", " @Binds Object ModuleWithErrors.object2(Long)", " in component: [IncludesModuleWithErrors]", "", "======================", "Full classname legend:", "======================", "IncludesModuleWithErrors: test.IncludesModuleWithErrors", "ModuleWithErrors: test.ModuleWithErrors", "========================", "End of classname legend:", "========================"); @Test public void moduleWithErrors_validationTypeNone() { CompilerTests.daggerCompiler(MODULE_WITH_ERRORS) .compile( subject -> { subject.hasErrorCount(0); subject.hasWarningCount(0); }); } @Test public void moduleWithErrors_validationTypeError() { CompilerTests.daggerCompiler(MODULE_WITH_ERRORS) .withProcessingOptions(ImmutableMap.of("dagger.fullBindingGraphValidation", "ERROR")) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContainingMatch(MODULE_WITH_ERRORS_MESSAGE.pattern()) .onSource(MODULE_WITH_ERRORS) .onLineContaining("interface ModuleWithErrors"); }); } @Test public void moduleWithErrors_validationTypeWarning() { CompilerTests.daggerCompiler(MODULE_WITH_ERRORS) .withProcessingOptions(ImmutableMap.of("dagger.fullBindingGraphValidation", "WARNING")) .compile( subject -> { subject.hasErrorCount(0); subject.hasWarningCount(1); subject.hasWarningContainingMatch(MODULE_WITH_ERRORS_MESSAGE.pattern()) .onSource(MODULE_WITH_ERRORS) .onLineContaining("interface ModuleWithErrors"); }); } private static final Source INCLUDES_MODULE_WITH_ERRORS = CompilerTests.javaSource( "test.IncludesModuleWithErrors", "package test;", "", "import dagger.Binds;", "import dagger.Module;", "", "@Module(includes = ModuleWithErrors.class)", "interface IncludesModuleWithErrors {}"); @Test public void includesModuleWithErrors_validationTypeNone() { CompilerTests.daggerCompiler(MODULE_WITH_ERRORS, INCLUDES_MODULE_WITH_ERRORS) .compile( subject -> { subject.hasErrorCount(0); subject.hasWarningCount(0); }); } @Test public void includesModuleWithErrors_validationTypeError() { CompilerTests.daggerCompiler(MODULE_WITH_ERRORS, INCLUDES_MODULE_WITH_ERRORS) .withProcessingOptions(ImmutableMap.of("dagger.fullBindingGraphValidation", "ERROR")) .compile( subject -> { subject.hasErrorCount(2); subject.hasErrorContainingMatch(MODULE_WITH_ERRORS_MESSAGE.pattern()) .onSource(MODULE_WITH_ERRORS) .onLineContaining("interface ModuleWithErrors"); subject.hasErrorContaining("ModuleWithErrors has errors") .onSource(INCLUDES_MODULE_WITH_ERRORS) .onLineContaining("ModuleWithErrors.class"); }); } @Test public void includesModuleWithErrors_validationTypeWarning() { CompilerTests.daggerCompiler(MODULE_WITH_ERRORS, INCLUDES_MODULE_WITH_ERRORS) .withProcessingOptions(ImmutableMap.of("dagger.fullBindingGraphValidation", "WARNING")) .compile( subject -> { subject.hasErrorCount(0); subject.hasWarningCount(2); subject.hasWarningContainingMatch(MODULE_WITH_ERRORS_MESSAGE.pattern()) .onSource(MODULE_WITH_ERRORS) .onLineContaining("interface ModuleWithErrors"); subject.hasWarningContainingMatch(INCLUDES_MODULE_WITH_ERRORS_MESSAGE.pattern()) .onSource(INCLUDES_MODULE_WITH_ERRORS) .onLineContaining("interface IncludesModuleWithErrors"); }); } private static final Source A_MODULE = CompilerTests.javaSource( "test.AModule", "package test;", "", "import dagger.Binds;", "import dagger.Module;", "", "@Module", "interface AModule {", " @Binds Object object(String string);", "}"); private static final Source COMBINED_WITH_A_MODULE_HAS_ERRORS = CompilerTests.javaSource( "test.CombinedWithAModuleHasErrors", "package test;", "", "import dagger.Binds;", "import dagger.Module;", "", "@Module(includes = AModule.class)", "interface CombinedWithAModuleHasErrors {", " @Binds Object object(Long l);", "}"); // Make sure the error doesn't show other bindings or a dependency trace afterwards. private static final Pattern COMBINED_WITH_A_MODULE_HAS_ERRORS_MESSAGE = endsWithMessage( "\033[1;31m[Dagger/DuplicateBindings]\033[0m Object is bound multiple times:", " @Binds Object AModule.object(String)", " @Binds Object CombinedWithAModuleHasErrors.object(Long)", " in component: [CombinedWithAModuleHasErrors]", "", "======================", "Full classname legend:", "======================", "AModule: test.AModule", "CombinedWithAModuleHasErrors: test.CombinedWithAModuleHasErrors", "========================", "End of classname legend:", "========================"); @Test public void moduleIncludingModuleWithCombinedErrors_validationTypeNone() { CompilerTests.daggerCompiler(A_MODULE, COMBINED_WITH_A_MODULE_HAS_ERRORS) .compile( subject -> { subject.hasErrorCount(0); subject.hasWarningCount(0); }); } @Test public void moduleIncludingModuleWithCombinedErrors_validationTypeError() { CompilerTests.daggerCompiler(A_MODULE, COMBINED_WITH_A_MODULE_HAS_ERRORS) .withProcessingOptions(ImmutableMap.of("dagger.fullBindingGraphValidation", "ERROR")) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContainingMatch(COMBINED_WITH_A_MODULE_HAS_ERRORS_MESSAGE.pattern()) .onSource(COMBINED_WITH_A_MODULE_HAS_ERRORS) .onLineContaining("interface CombinedWithAModuleHasErrors"); }); } @Test public void moduleIncludingModuleWithCombinedErrors_validationTypeWarning() { CompilerTests.daggerCompiler(A_MODULE, COMBINED_WITH_A_MODULE_HAS_ERRORS) .withProcessingOptions(ImmutableMap.of("dagger.fullBindingGraphValidation", "WARNING")) .compile( subject -> { subject.hasErrorCount(0); subject.hasWarningCount(1); subject.hasWarningContainingMatch(COMBINED_WITH_A_MODULE_HAS_ERRORS_MESSAGE.pattern()) .onSource(COMBINED_WITH_A_MODULE_HAS_ERRORS) .onLineContaining("interface CombinedWithAModuleHasErrors"); }); } private static final Source SUBCOMPONENT_WITH_ERRORS = CompilerTests.javaSource( "test.SubcomponentWithErrors", "package test;", "", "import dagger.BindsInstance;", "import dagger.Subcomponent;", "", "@Subcomponent(modules = AModule.class)", "interface SubcomponentWithErrors {", " @Subcomponent.Builder", " interface Builder {", " @BindsInstance Builder object(Object object);", " SubcomponentWithErrors build();", " }", "}"); // Make sure the error doesn't show other bindings or a dependency trace afterwards. private static final Pattern SUBCOMPONENT_WITH_ERRORS_MESSAGE = endsWithMessage( "\033[1;31m[Dagger/DuplicateBindings]\033[0m Object is bound multiple times:", " @Binds Object AModule.object(String)", " @BindsInstance SubcomponentWithErrors.Builder" + " SubcomponentWithErrors.Builder.object(Object)", " in component: [SubcomponentWithErrors]", "", "======================", "Full classname legend:", "======================", "AModule: test.AModule", "SubcomponentWithErrors: test.SubcomponentWithErrors", "========================", "End of classname legend:", "========================"); private static final Pattern MODULE_WITH_SUBCOMPONENT_WITH_ERRORS_MESSAGE = endsWithMessage( "\033[1;31m[Dagger/DuplicateBindings]\033[0m Object is bound multiple times:", " @Binds Object AModule.object(String)", " @BindsInstance SubcomponentWithErrors.Builder" + " SubcomponentWithErrors.Builder.object(Object)", " in component: [ModuleWithSubcomponentWithErrors → SubcomponentWithErrors]", "", "======================", "Full classname legend:", "======================", "AModule: test.AModule", "ModuleWithSubcomponentWithErrors: test.ModuleWithSubcomponentWithErrors", "SubcomponentWithErrors: test.SubcomponentWithErrors", "========================", "End of classname legend:", "========================"); @Test public void subcomponentWithErrors_validationTypeNone() { CompilerTests.daggerCompiler(SUBCOMPONENT_WITH_ERRORS, A_MODULE) .compile( subject -> { subject.hasErrorCount(0); subject.hasWarningCount(0); }); } @Test public void subcomponentWithErrors_validationTypeError() { CompilerTests.daggerCompiler(SUBCOMPONENT_WITH_ERRORS, A_MODULE) .withProcessingOptions(ImmutableMap.of("dagger.fullBindingGraphValidation", "ERROR")) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContainingMatch(SUBCOMPONENT_WITH_ERRORS_MESSAGE.pattern()) .onSource(SUBCOMPONENT_WITH_ERRORS) .onLineContaining("interface SubcomponentWithErrors"); }); } @Test public void subcomponentWithErrors_validationTypeWarning() { CompilerTests.daggerCompiler(SUBCOMPONENT_WITH_ERRORS, A_MODULE) .withProcessingOptions(ImmutableMap.of("dagger.fullBindingGraphValidation", "WARNING")) .compile( subject -> { subject.hasErrorCount(0); subject.hasWarningCount(1); subject.hasWarningContainingMatch(SUBCOMPONENT_WITH_ERRORS_MESSAGE.pattern()) .onSource(SUBCOMPONENT_WITH_ERRORS) .onLineContaining("interface SubcomponentWithErrors"); }); } private static final Source MODULE_WITH_SUBCOMPONENT_WITH_ERRORS = CompilerTests.javaSource( "test.ModuleWithSubcomponentWithErrors", "package test;", "", "import dagger.Binds;", "import dagger.Module;", "", "@Module(subcomponents = SubcomponentWithErrors.class)", "interface ModuleWithSubcomponentWithErrors {}"); @Test public void moduleWithSubcomponentWithErrors_validationTypeNone() { CompilerTests.daggerCompiler( MODULE_WITH_SUBCOMPONENT_WITH_ERRORS, SUBCOMPONENT_WITH_ERRORS, A_MODULE) .compile( subject -> { subject.hasErrorCount(0); subject.hasWarningCount(0); }); } @Test public void moduleWithSubcomponentWithErrors_validationTypeError() { CompilerTests.daggerCompiler( MODULE_WITH_SUBCOMPONENT_WITH_ERRORS, SUBCOMPONENT_WITH_ERRORS, A_MODULE) .withProcessingOptions(ImmutableMap.of("dagger.fullBindingGraphValidation", "ERROR")) .compile( subject -> { subject.hasErrorCount(2); subject.hasErrorContainingMatch( MODULE_WITH_SUBCOMPONENT_WITH_ERRORS_MESSAGE.pattern()) .onSource(MODULE_WITH_SUBCOMPONENT_WITH_ERRORS) .onLineContaining("interface ModuleWithSubcomponentWithErrors"); // TODO(b/130283677) subject.hasErrorContainingMatch(SUBCOMPONENT_WITH_ERRORS_MESSAGE.pattern()) .onSource(SUBCOMPONENT_WITH_ERRORS) .onLineContaining("interface SubcomponentWithErrors"); }); } @Test public void moduleWithSubcomponentWithErrors_validationTypeWarning() { CompilerTests.daggerCompiler( MODULE_WITH_SUBCOMPONENT_WITH_ERRORS, SUBCOMPONENT_WITH_ERRORS, A_MODULE) .withProcessingOptions(ImmutableMap.of("dagger.fullBindingGraphValidation", "WARNING")) .compile( subject -> { subject.hasErrorCount(0); subject.hasWarningCount(2); subject.hasWarningContainingMatch( MODULE_WITH_SUBCOMPONENT_WITH_ERRORS_MESSAGE.pattern()) .onSource(MODULE_WITH_SUBCOMPONENT_WITH_ERRORS) .onLineContaining("interface ModuleWithSubcomponentWithErrors"); // TODO(b/130283677): Don't repeat error. subject.hasWarningContainingMatch(SUBCOMPONENT_WITH_ERRORS_MESSAGE.pattern()) .onSource(SUBCOMPONENT_WITH_ERRORS) .onLineContaining("interface SubcomponentWithErrors"); }); } private static final Source A_SUBCOMPONENT = CompilerTests.javaSource( "test.ASubcomponent", "package test;", "", "import dagger.BindsInstance;", "import dagger.Subcomponent;", "", "@Subcomponent(modules = AModule.class)", "interface ASubcomponent {", " @Subcomponent.Builder", " interface Builder {", " ASubcomponent build();", " }", "}"); private static final Source COMBINED_WITH_A_SUBCOMPONENT_HAS_ERRORS = CompilerTests.javaSource( "test.CombinedWithASubcomponentHasErrors", "package test;", "", "import dagger.Binds;", "import dagger.Module;", "", "@Module(subcomponents = ASubcomponent.class)", "interface CombinedWithASubcomponentHasErrors {", " @Binds Object object(Number number);", "}"); // Make sure the error doesn't show other bindings or a dependency trace afterwards. private static final Pattern COMBINED_WITH_A_SUBCOMPONENT_HAS_ERRORS_MESSAGE = endsWithMessage( "\033[1;31m[Dagger/DuplicateBindings]\033[0m Object is bound multiple times:", " @Binds Object AModule.object(String)", " @Binds Object CombinedWithASubcomponentHasErrors.object(Number)", " in component: [CombinedWithASubcomponentHasErrors → ASubcomponent]", "", "======================", "Full classname legend:", "======================", "AModule: test.AModule", "ASubcomponent: test.ASubcomponent", "CombinedWithASubcomponentHasErrors: test.CombinedWithASubcomponentHasErrors", "========================", "End of classname legend:", "========================"); @Test public void moduleWithSubcomponentWithCombinedErrors_validationTypeNone() { CompilerTests.daggerCompiler(COMBINED_WITH_A_SUBCOMPONENT_HAS_ERRORS, A_SUBCOMPONENT, A_MODULE) .compile( subject -> { subject.hasErrorCount(0); subject.hasWarningCount(0); }); } @Test public void moduleWithSubcomponentWithCombinedErrors_validationTypeError() { CompilerTests.daggerCompiler(COMBINED_WITH_A_SUBCOMPONENT_HAS_ERRORS, A_SUBCOMPONENT, A_MODULE) .withProcessingOptions(ImmutableMap.of("dagger.fullBindingGraphValidation", "ERROR")) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContainingMatch( COMBINED_WITH_A_SUBCOMPONENT_HAS_ERRORS_MESSAGE.pattern()) .onSource(COMBINED_WITH_A_SUBCOMPONENT_HAS_ERRORS) .onLineContaining("interface CombinedWithASubcomponentHasErrors"); }); } @Test public void moduleWithSubcomponentWithCombinedErrors_validationTypeWarning() { CompilerTests.daggerCompiler(COMBINED_WITH_A_SUBCOMPONENT_HAS_ERRORS, A_SUBCOMPONENT, A_MODULE) .withProcessingOptions(ImmutableMap.of("dagger.fullBindingGraphValidation", "WARNING")) .compile( subject -> { subject.hasErrorCount(0); subject.hasWarningCount(1); subject.hasWarningContainingMatch( COMBINED_WITH_A_SUBCOMPONENT_HAS_ERRORS_MESSAGE.pattern()) .onSource(COMBINED_WITH_A_SUBCOMPONENT_HAS_ERRORS) .onLineContaining("interface CombinedWithASubcomponentHasErrors"); }); } @Test public void bothAliasesDifferentValues() { CompilerTests.daggerCompiler(MODULE_WITH_ERRORS) .withProcessingOptions( ImmutableMap.of( "dagger.moduleBindingValidation", "NONE", "dagger.fullBindingGraphValidation", "ERROR")) .compile( subject -> { subject.hasErrorCount(1); subject.hasErrorContaining( "Only one of the equivalent options " + "(-Adagger.fullBindingGraphValidation, -Adagger.moduleBindingValidation)" + " should be used; prefer -Adagger.fullBindingGraphValidation"); }); } @Test public void bothAliasesSameValue() { CompilerTests.daggerCompiler(MODULE_WITH_ERRORS) .withProcessingOptions( ImmutableMap.of( "dagger.moduleBindingValidation", "NONE", "dagger.fullBindingGraphValidation", "NONE")) .compile( subject -> { subject.hasErrorCount(0); subject.hasWarningCount(1); subject.hasWarningContaining( "Only one of the equivalent options " + "(-Adagger.fullBindingGraphValidation, -Adagger.moduleBindingValidation)" + " should be used; prefer -Adagger.fullBindingGraphValidation"); }); } }