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,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,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 377 @Test mapBindingOfProvider_provides()378 public void mapBindingOfProvider_provides() { 379 Source providesModule = 380 CompilerTests.javaSource( 381 "test.MapModule", 382 "package test;", 383 "", 384 "import dagger.Module;", 385 "import dagger.Provides;", 386 "import dagger.multibindings.IntoMap;", 387 "import dagger.multibindings.StringKey;", 388 "import javax.inject.Provider;", 389 "", 390 "@Module", 391 "abstract class MapModule {", 392 "", 393 " @Provides", 394 " @IntoMap", 395 " @StringKey(\"foo\")", 396 " static Provider<String> provideProvider() {", 397 " return null;", 398 " }", 399 "}"); 400 401 // Entry points aren't needed because the check we care about here is a module validation 402 Source providesComponent = component(""); 403 404 CompilerTests.daggerCompiler(providesModule, providesComponent) 405 .withProcessingOptions(compilerMode.processorOptions()) 406 .compile( 407 subject -> { 408 subject.hasErrorCount(2); 409 subject.hasErrorContaining( 410 "@Provides methods with @IntoMap must not return framework types"); 411 subject.hasErrorContaining("test.MapModule has errors") 412 .onSource(providesComponent) 413 .onLineContaining("@Component(modules = {MapModule.class})"); 414 }); 415 } 416 417 @Test mapBindingOfProvider_binds()418 public void mapBindingOfProvider_binds() { 419 Source bindsModule = 420 CompilerTests.javaSource( 421 "test.MapModule", 422 "package test;", 423 "", 424 "import dagger.Module;", 425 "import dagger.Binds;", 426 "import dagger.multibindings.IntoMap;", 427 "import dagger.multibindings.StringKey;", 428 "import javax.inject.Provider;", 429 "", 430 "@Module", 431 "abstract class MapModule {", 432 "", 433 " @Binds", 434 " @IntoMap", 435 " @StringKey(\"foo\")", 436 " abstract Provider<String> provideProvider(Provider<String> provider);", 437 "}"); 438 439 // Entry points aren't needed because the check we care about here is a module validation 440 Source bindsComponent = component(""); 441 442 CompilerTests.daggerCompiler(bindsModule, bindsComponent) 443 .withProcessingOptions(compilerMode.processorOptions()) 444 .compile( 445 subject -> { 446 subject.hasErrorCount(2); 447 subject.hasErrorContaining( 448 "@Binds methods with @IntoMap must not return framework types"); 449 subject.hasErrorContaining("test.MapModule has errors") 450 .onSource(bindsComponent) 451 .onLineContaining("@Component(modules = {MapModule.class})"); 452 }); 453 } 454 component(String... entryPoints)455 private static Source component(String... entryPoints) { 456 return CompilerTests.javaSource( 457 "test.TestComponent", 458 ImmutableList.<String>builder() 459 .add( 460 "package test;", 461 "", 462 "import dagger.Component;", 463 "import dagger.producers.Producer;", 464 "import java.util.Map;", 465 "import javax.inject.Provider;", 466 "", 467 "@Component(modules = {MapModule.class})", 468 "interface TestComponent {") 469 .add(entryPoints) 470 .add("}") 471 .build()); 472 } 473 } 474