1 /* 2 * Copyright (C) 2014 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.internal.codegen; 18 19 import androidx.room.compiler.processing.util.Source; 20 import com.google.common.collect.ImmutableList; 21 import com.google.common.collect.ImmutableMap; 22 import dagger.testing.compile.CompilerTests; 23 import org.junit.Test; 24 import org.junit.runner.RunWith; 25 import org.junit.runners.Parameterized; 26 import org.junit.runners.Parameterized.Parameters; 27 28 @RunWith(Parameterized.class) 29 public class MapMultibindingValidationTest { 30 @Parameters(name = "{0}") parameters()31 public static ImmutableList<Object[]> parameters() { 32 return CompilerMode.TEST_PARAMETERS; 33 } 34 35 private final CompilerMode compilerMode; 36 MapMultibindingValidationTest(CompilerMode compilerMode)37 public MapMultibindingValidationTest(CompilerMode compilerMode) { 38 this.compilerMode = compilerMode; 39 } 40 41 @Test duplicateMapKeys_UnwrappedMapKey()42 public void duplicateMapKeys_UnwrappedMapKey() { 43 Source module = 44 CompilerTests.javaSource( 45 "test.MapModule", 46 "package test;", 47 "", 48 "import dagger.Module;", 49 "import dagger.Provides;", 50 "import dagger.multibindings.StringKey;", 51 "import dagger.multibindings.IntoMap;", 52 "", 53 "@Module", 54 "final class MapModule {", 55 " @Provides @IntoMap @StringKey(\"AKey\") Object provideObjectForAKey() {", 56 " return \"one\";", 57 " }", 58 "", 59 " @Provides @IntoMap @StringKey(\"AKey\") Object provideObjectForAKeyAgain() {", 60 " return \"one again\";", 61 " }", 62 "}"); 63 64 // If they're all there, report only Map<K, V>. 65 CompilerTests.daggerCompiler( 66 module, 67 component( 68 "Map<String, Object> objects();", 69 "Map<String, Provider<Object>> objectProviders();", 70 "Producer<Map<String, Producer<Object>>> objectProducers();")) 71 .withProcessingOptions(compilerMode.processorOptions()) 72 .compile( 73 subject -> { 74 subject.hasErrorCount(1); 75 subject.hasErrorContaining( 76 "The same map key is bound more than once for Map<String,Object>"); 77 subject.hasErrorContaining("provideObjectForAKey()"); 78 subject.hasErrorContaining("provideObjectForAKeyAgain()"); 79 }); 80 81 CompilerTests.daggerCompiler(module) 82 .withProcessingOptions( 83 ImmutableMap.<String, String>builder() 84 .putAll(compilerMode.processorOptions()) 85 .put("dagger.fullBindingGraphValidation", "ERROR") 86 .buildOrThrow()) 87 .compile( 88 subject -> { 89 subject.hasErrorCount(1); 90 subject.hasErrorContaining( 91 "The same map key is bound more than once for Map<String,Provider<Object>>") 92 .onSource(module) 93 .onLineContaining("class MapModule"); 94 subject.hasErrorContaining("provideObjectForAKey()"); 95 subject.hasErrorContaining("provideObjectForAKeyAgain()"); 96 }); 97 98 // If there's Map<K, V> and Map<K, Provider<V>>, report only Map<K, V>. 99 CompilerTests.daggerCompiler( 100 module, 101 component( 102 "Map<String, Object> objects();", 103 "Map<String, Provider<Object>> objectProviders();")) 104 .withProcessingOptions(compilerMode.processorOptions()) 105 .compile( 106 subject -> { 107 subject.hasErrorCount(1); 108 subject.hasErrorContaining( 109 "The same map key is bound more than once for Map<String,Object>"); 110 }); 111 112 // If there's Map<K, V> and Map<K, Producer<V>>, report only Map<K, V>. 113 CompilerTests.daggerCompiler( 114 module, 115 component( 116 "Map<String, Object> objects();", 117 "Producer<Map<String, Producer<Object>>> objectProducers();")) 118 .withProcessingOptions(compilerMode.processorOptions()) 119 .compile( 120 subject -> { 121 subject.hasErrorCount(1); 122 subject.hasErrorContaining( 123 "The same map key is bound more than once for Map<String,Object>"); 124 }); 125 126 // If there's Map<K, Provider<V>> and Map<K, Producer<V>>, report only Map<K, Provider<V>>. 127 CompilerTests.daggerCompiler( 128 module, 129 component( 130 "Map<String, Provider<Object>> objectProviders();", 131 "Producer<Map<String, Producer<Object>>> objectProducers();")) 132 .withProcessingOptions(compilerMode.processorOptions()) 133 .compile( 134 subject -> { 135 subject.hasErrorCount(1); 136 subject.hasErrorContaining( 137 "The same map key is bound more than once for Map<String,Provider<Object>>"); 138 }); 139 140 CompilerTests.daggerCompiler( 141 module, 142 component("Map<String, Object> objects();")) 143 .withProcessingOptions(compilerMode.processorOptions()) 144 .compile( 145 subject -> { 146 subject.hasErrorCount(1); 147 subject.hasErrorContaining( 148 "The same map key is bound more than once for Map<String,Object>"); 149 }); 150 151 CompilerTests.daggerCompiler( 152 module, 153 component("Map<String, Provider<Object>> objectProviders();")) 154 .withProcessingOptions(compilerMode.processorOptions()) 155 .compile( 156 subject -> { 157 subject.hasErrorCount(1); 158 subject.hasErrorContaining( 159 "The same map key is bound more than once for Map<String,Provider<Object>>"); 160 }); 161 162 CompilerTests.daggerCompiler( 163 module, 164 component("Producer<Map<String, Producer<Object>>> objectProducers();")) 165 .withProcessingOptions(compilerMode.processorOptions()) 166 .compile( 167 subject -> { 168 subject.hasErrorCount(1); 169 subject.hasErrorContaining( 170 "The same map key is bound more than once for Map<String,Producer<Object>>"); 171 }); 172 } 173 174 @Test duplicateMapKeys_WrappedMapKey()175 public void duplicateMapKeys_WrappedMapKey() { 176 Source module = 177 CompilerTests.javaSource( 178 "test.MapModule", 179 "package test;", 180 "", 181 "import dagger.Module;", 182 "import dagger.Provides;", 183 "import dagger.multibindings.IntoMap;", 184 "import dagger.MapKey;", 185 "", 186 "@Module", 187 "abstract class MapModule {", 188 "", 189 " @MapKey(unwrapValue = false)", 190 " @interface WrappedMapKey {", 191 " String value();", 192 " }", 193 "", 194 " @Provides", 195 " @IntoMap", 196 " @WrappedMapKey(\"foo\")", 197 " static String stringMapEntry1() { return \"\"; }", 198 "", 199 " @Provides", 200 " @IntoMap", 201 " @WrappedMapKey(\"foo\")", 202 " static String stringMapEntry2() { return \"\"; }", 203 "}"); 204 205 Source component = component("Map<test.MapModule.WrappedMapKey, String> objects();"); 206 207 CompilerTests.daggerCompiler(module, component) 208 .withProcessingOptions(compilerMode.processorOptions()) 209 .compile( 210 subject -> { 211 subject.hasErrorCount(1); 212 subject.hasErrorContaining( 213 String.join( 214 "\n", 215 "\033[1;31m[Dagger/MapKeys]\033[0m The same map key is bound more than " 216 + "once for Map<MapModule.WrappedMapKey,String>", 217 " @Provides @IntoMap @MapModule.WrappedMapKey(\"foo\") String " 218 + "MapModule.stringMapEntry1()", 219 " @Provides @IntoMap @MapModule.WrappedMapKey(\"foo\") String " 220 + "MapModule.stringMapEntry2()")) 221 .onSource(component) 222 .onLineContaining("interface TestComponent"); 223 }); 224 } 225 226 @Test inconsistentMapKeyAnnotations()227 public void inconsistentMapKeyAnnotations() { 228 Source module = 229 CompilerTests.javaSource( 230 "test.MapModule", 231 "package test;", 232 "", 233 "import dagger.Module;", 234 "import dagger.Provides;", 235 "import dagger.multibindings.StringKey;", 236 "import dagger.multibindings.IntoMap;", 237 "", 238 "@Module", 239 "final class MapModule {", 240 " @Provides @IntoMap @StringKey(\"AKey\") Object provideObjectForAKey() {", 241 " return \"one\";", 242 " }", 243 "", 244 " @Provides @IntoMap @StringKeyTwo(\"BKey\") Object provideObjectForBKey() {", 245 " return \"two\";", 246 " }", 247 "}"); 248 Source stringKeyTwoFile = 249 CompilerTests.javaSource( 250 "test.StringKeyTwo", 251 "package test;", 252 "", 253 "import dagger.MapKey;", 254 "", 255 "@MapKey(unwrapValue = true)", 256 "public @interface StringKeyTwo {", 257 " String value();", 258 "}"); 259 260 // If they're all there, report only Map<K, V>. 261 CompilerTests.daggerCompiler( 262 module, 263 stringKeyTwoFile, 264 component( 265 "Map<String, Object> objects();", 266 "Map<String, Provider<Object>> objectProviders();", 267 "Producer<Map<String, Producer<Object>>> objectProducers();")) 268 .withProcessingOptions(compilerMode.processorOptions()) 269 .compile( 270 subject -> { 271 subject.hasErrorCount(1); 272 subject.hasErrorContaining( 273 "Map<String,Object> uses more than one @MapKey annotation type"); 274 subject.hasErrorContaining("provideObjectForAKey()"); 275 subject.hasErrorContaining("provideObjectForBKey()"); 276 }); 277 278 CompilerTests.daggerCompiler(module, stringKeyTwoFile) 279 .withProcessingOptions( 280 ImmutableMap.<String, String>builder() 281 .putAll(compilerMode.processorOptions()) 282 .put("dagger.fullBindingGraphValidation", "ERROR") 283 .buildOrThrow()) 284 .compile( 285 subject -> { 286 subject.hasErrorCount(1); 287 subject.hasErrorContaining( 288 "Map<String,Provider<Object>> uses more than one @MapKey annotation type") 289 .onSource(module) 290 .onLineContaining("class MapModule"); 291 subject.hasErrorContaining("provideObjectForAKey()"); 292 subject.hasErrorContaining("provideObjectForBKey()"); 293 }); 294 295 // If there's Map<K, V> and Map<K, Provider<V>>, report only Map<K, V>. 296 CompilerTests.daggerCompiler( 297 module, 298 stringKeyTwoFile, 299 component( 300 "Map<String, Object> objects();", 301 "Map<String, Provider<Object>> objectProviders();")) 302 .withProcessingOptions(compilerMode.processorOptions()) 303 .compile( 304 subject -> { 305 subject.hasErrorCount(1); 306 subject.hasErrorContaining( 307 "Map<String,Object> uses more than one @MapKey annotation type"); 308 }); 309 310 // If there's Map<K, V> and Map<K, Producer<V>>, report only Map<K, V>. 311 CompilerTests.daggerCompiler( 312 module, 313 stringKeyTwoFile, 314 component( 315 "Map<String, Object> objects();", 316 "Producer<Map<String, Producer<Object>>> objectProducers();")) 317 .withProcessingOptions(compilerMode.processorOptions()) 318 .compile( 319 subject -> { 320 subject.hasErrorCount(1); 321 subject.hasErrorContaining( 322 "Map<String,Object> uses more than one @MapKey annotation type"); 323 }); 324 325 // If there's Map<K, Provider<V>> and Map<K, Producer<V>>, report only Map<K, Provider<V>>. 326 CompilerTests.daggerCompiler( 327 module, 328 stringKeyTwoFile, 329 component( 330 "Map<String, Provider<Object>> objectProviders();", 331 "Producer<Map<String, Producer<Object>>> objectProducers();")) 332 .withProcessingOptions(compilerMode.processorOptions()) 333 .compile( 334 subject -> { 335 subject.hasErrorCount(1); 336 subject.hasErrorContaining( 337 "Map<String,Provider<Object>> uses more than one @MapKey annotation type"); 338 }); 339 340 CompilerTests.daggerCompiler( 341 module, 342 stringKeyTwoFile, 343 component("Map<String, Object> objects();")) 344 .withProcessingOptions(compilerMode.processorOptions()) 345 .compile( 346 subject -> { 347 subject.hasErrorCount(1); 348 subject.hasErrorContaining( 349 "Map<String,Object> uses more than one @MapKey annotation type"); 350 }); 351 352 CompilerTests.daggerCompiler( 353 module, 354 stringKeyTwoFile, 355 component("Map<String, Provider<Object>> objectProviders();")) 356 .withProcessingOptions(compilerMode.processorOptions()) 357 .compile( 358 subject -> { 359 subject.hasErrorCount(1); 360 subject.hasErrorContaining( 361 "Map<String,Provider<Object>> uses more than one @MapKey annotation type"); 362 }); 363 364 CompilerTests.daggerCompiler( 365 module, 366 stringKeyTwoFile, 367 component("Producer<Map<String, Producer<Object>>> objectProducers();")) 368 .withProcessingOptions(compilerMode.processorOptions()) 369 .compile( 370 subject -> { 371 subject.hasErrorCount(1); 372 subject.hasErrorContaining( 373 "Map<String,Producer<Object>> uses more than one @MapKey annotation type"); 374 }); 375 } 376 component(String... entryPoints)377 private static Source component(String... entryPoints) { 378 return CompilerTests.javaSource( 379 "test.TestComponent", 380 ImmutableList.<String>builder() 381 .add( 382 "package test;", 383 "", 384 "import dagger.Component;", 385 "import dagger.producers.Producer;", 386 "import java.util.Map;", 387 "import javax.inject.Provider;", 388 "", 389 "@Component(modules = {MapModule.class})", 390 "interface TestComponent {") 391 .add(entryPoints) 392 .add("}") 393 .build()); 394 } 395 } 396