/* * 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 static dagger.internal.codegen.TestUtils.message; 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.JUnit4; @RunWith(JUnit4.class) public class MapMultibindingValidationTest { @Test public void duplicateMapKeys_UnwrappedMapKey() { JavaFileObject module = JavaFileObjects.forSourceLines( "test.MapModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.multibindings.StringKey;", "import dagger.multibindings.IntoMap;", "", "@Module", "final class MapModule {", " @Provides @IntoMap @StringKey(\"AKey\") Object provideObjectForAKey() {", " return \"one\";", " }", "", " @Provides @IntoMap @StringKey(\"AKey\") Object provideObjectForAKeyAgain() {", " return \"one again\";", " }", "}"); // If they're all there, report only Map. Compilation compilation = daggerCompiler() .compile( module, component( "Map objects();", "Map> objectProviders();", "Producer>> objectProducers();")); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( "The same map key is bound more than once for " + "Map"); assertThat(compilation).hadErrorContaining("provideObjectForAKey()"); assertThat(compilation).hadErrorContaining("provideObjectForAKeyAgain()"); assertThat(compilation).hadErrorCount(1); compilation = compilerWithOptions("-Adagger.fullBindingGraphValidation=ERROR") .compile(module); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( "The same map key is bound more than once for " + "Map>") .inFile(module) .onLineContaining("class MapModule"); assertThat(compilation).hadErrorContaining("provideObjectForAKey()"); assertThat(compilation).hadErrorContaining("provideObjectForAKeyAgain()"); assertThat(compilation).hadErrorCount(1); // If there's Map and Map>, report only Map. compilation = daggerCompiler() .compile( module, component( "Map objects();", "Map> objectProviders();")); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( "The same map key is bound more than once for " + "Map"); assertThat(compilation).hadErrorCount(1); // If there's Map and Map>, report only Map. compilation = daggerCompiler() .compile( module, component( "Map objects();", "Producer>> objectProducers();")); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( "The same map key is bound more than once for " + "Map"); assertThat(compilation).hadErrorCount(1); // If there's Map> and Map>, report only Map>. compilation = daggerCompiler() .compile( module, component( "Map> objectProviders();", "Producer>> objectProducers();")); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( "The same map key is bound more than once for " + "Map>"); assertThat(compilation).hadErrorCount(1); compilation = daggerCompiler().compile(module, component("Map objects();")); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( "The same map key is bound more than once for " + "Map"); assertThat(compilation).hadErrorCount(1); compilation = daggerCompiler() .compile(module, component("Map> objectProviders();")); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( "The same map key is bound more than once for " + "Map>"); assertThat(compilation).hadErrorCount(1); compilation = daggerCompiler() .compile( module, component("Producer>> objectProducers();")); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( "The same map key is bound more than once for " + "Map>"); assertThat(compilation).hadErrorCount(1); } @Test public void duplicateMapKeys_WrappedMapKey() { JavaFileObject module = JavaFileObjects.forSourceLines( "test.MapModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.multibindings.IntoMap;", "import dagger.MapKey;", "", "@Module", "abstract class MapModule {", "", " @MapKey(unwrapValue = false)", " @interface WrappedMapKey {", " String value();", " }", "", " @Provides", " @IntoMap", " @WrappedMapKey(\"foo\")", " static String stringMapEntry1() { return \"\"; }", "", " @Provides", " @IntoMap", " @WrappedMapKey(\"foo\")", " static String stringMapEntry2() { return \"\"; }", "}"); JavaFileObject component = component("Map objects();"); Compilation compilation = daggerCompiler().compile(component, module); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( message( "\033[1;31m[Dagger/MapKeys]\033[0m The same map key is bound more than once for " + "Map", " @Provides @IntoMap @MapModule.WrappedMapKey(\"foo\") String " + "MapModule.stringMapEntry1()", " @Provides @IntoMap @MapModule.WrappedMapKey(\"foo\") String " + "MapModule.stringMapEntry2()")) .inFile(component) .onLineContaining("interface TestComponent"); } @Test public void inconsistentMapKeyAnnotations() { JavaFileObject module = JavaFileObjects.forSourceLines( "test.MapModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import dagger.multibindings.StringKey;", "import dagger.multibindings.IntoMap;", "", "@Module", "final class MapModule {", " @Provides @IntoMap @StringKey(\"AKey\") Object provideObjectForAKey() {", " return \"one\";", " }", "", " @Provides @IntoMap @StringKeyTwo(\"BKey\") Object provideObjectForBKey() {", " return \"two\";", " }", "}"); JavaFileObject stringKeyTwoFile = JavaFileObjects.forSourceLines( "test.StringKeyTwo", "package test;", "", "import dagger.MapKey;", "", "@MapKey(unwrapValue = true)", "public @interface StringKeyTwo {", " String value();", "}"); // If they're all there, report only Map. Compilation compilation = daggerCompiler() .compile( module, stringKeyTwoFile, component( "Map objects();", "Map> objectProviders();", "Producer>> objectProducers();")); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( "Map" + " uses more than one @MapKey annotation type"); assertThat(compilation).hadErrorContaining("provideObjectForAKey()"); assertThat(compilation).hadErrorContaining("provideObjectForBKey()"); assertThat(compilation).hadErrorCount(1); compilation = compilerWithOptions("-Adagger.fullBindingGraphValidation=ERROR") .compile(module, stringKeyTwoFile); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( "Map>" + " uses more than one @MapKey annotation type") .inFile(module) .onLineContaining("class MapModule"); assertThat(compilation).hadErrorContaining("provideObjectForAKey()"); assertThat(compilation).hadErrorContaining("provideObjectForBKey()"); assertThat(compilation).hadErrorCount(1); // If there's Map and Map>, report only Map. compilation = daggerCompiler() .compile( module, stringKeyTwoFile, component( "Map objects();", "Map> objectProviders();")); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( "Map" + " uses more than one @MapKey annotation type"); assertThat(compilation).hadErrorCount(1); // If there's Map and Map>, report only Map. compilation = daggerCompiler() .compile( module, stringKeyTwoFile, component( "Map objects();", "Producer>> objectProducers();")); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( "Map" + " uses more than one @MapKey annotation type"); assertThat(compilation).hadErrorCount(1); // If there's Map> and Map>, report only Map>. compilation = daggerCompiler() .compile( module, stringKeyTwoFile, component( "Map> objectProviders();", "Producer>> objectProducers();")); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( "Map>" + " uses more than one @MapKey annotation type"); assertThat(compilation).hadErrorCount(1); compilation = daggerCompiler() .compile(module, stringKeyTwoFile, component("Map objects();")); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( "Map" + " uses more than one @MapKey annotation type"); assertThat(compilation).hadErrorCount(1); compilation = daggerCompiler() .compile( module, stringKeyTwoFile, component("Map> objectProviders();")); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( "Map>" + " uses more than one @MapKey annotation type"); assertThat(compilation).hadErrorCount(1); compilation = daggerCompiler() .compile( module, stringKeyTwoFile, component("Producer>> objectProducers();")); assertThat(compilation).failed(); assertThat(compilation) .hadErrorContaining( "Map>" + " uses more than one @MapKey annotation type"); assertThat(compilation).hadErrorCount(1); } private static JavaFileObject component(String... entryPoints) { return JavaFileObjects.forSourceLines( "test.TestComponent", ImmutableList.builder() .add( "package test;", "", "import dagger.Component;", "import dagger.producers.Producer;", "import java.util.Map;", "import javax.inject.Provider;", "", "@Component(modules = {MapModule.class})", "interface TestComponent {") .add(entryPoints) .add("}") .build()); } }