1 /* 2 * Copyright (C) 2014 Google, Inc. 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 // TODO(beder): Merge the error-handling tests with the ModuleFactoryGeneratorTest. 17 package dagger.internal.codegen; 18 19 import com.google.common.collect.ImmutableList; 20 import com.google.testing.compile.JavaFileObjects; 21 import javax.tools.JavaFileObject; 22 import org.junit.Test; 23 import org.junit.runner.RunWith; 24 import org.junit.runners.JUnit4; 25 26 import static com.google.common.truth.Truth.assertAbout; 27 import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; 28 import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; 29 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_ABSTRACT; 30 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_MUST_RETURN_A_VALUE; 31 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_NOT_IN_MODULE; 32 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_PRIVATE; 33 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_SET_VALUES_RAW_SET; 34 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_TYPE_PARAMETER; 35 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_WITH_SAME_NAME; 36 import static dagger.internal.codegen.ErrorMessages.PRODUCES_METHOD_RAW_FUTURE; 37 import static dagger.internal.codegen.ErrorMessages.PRODUCES_METHOD_RETURN_TYPE; 38 import static dagger.internal.codegen.ErrorMessages.PRODUCES_METHOD_SET_VALUES_RETURN_SET; 39 import static dagger.internal.codegen.ErrorMessages.PROVIDES_OR_PRODUCES_METHOD_MULTIPLE_QUALIFIERS; 40 41 @RunWith(JUnit4.class) 42 public class ProducerModuleFactoryGeneratorTest { 43 private String formatErrorMessage(String msg) { 44 return String.format(msg, "Produces"); 45 } 46 47 private String formatModuleErrorMessage(String msg) { 48 return String.format(msg, "Produces", "ProducerModule"); 49 } 50 51 @Test public void producesMethodNotInModule() { 52 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 53 "package test;", 54 "", 55 "import dagger.producers.Produces;", 56 "", 57 "final class TestModule {", 58 " @Produces String produceString() {", 59 " return \"\";", 60 " }", 61 "}"); 62 assertAbout(javaSource()).that(moduleFile) 63 .processedWith(new ComponentProcessor()) 64 .failsToCompile() 65 .withErrorContaining(formatModuleErrorMessage(BINDING_METHOD_NOT_IN_MODULE)); 66 } 67 68 @Test public void producesMethodAbstract() { 69 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 70 "package test;", 71 "", 72 "import dagger.producers.ProducerModule;", 73 "import dagger.producers.Produces;", 74 "", 75 "@ProducerModule", 76 "abstract class TestModule {", 77 " @Produces abstract String produceString();", 78 "}"); 79 assertAbout(javaSource()).that(moduleFile) 80 .processedWith(new ComponentProcessor()) 81 .failsToCompile() 82 .withErrorContaining(formatErrorMessage(BINDING_METHOD_ABSTRACT)); 83 } 84 85 @Test public void producesMethodPrivate() { 86 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 87 "package test;", 88 "", 89 "import dagger.producers.ProducerModule;", 90 "import dagger.producers.Produces;", 91 "", 92 "@ProducerModule", 93 "final class TestModule {", 94 " @Produces private String produceString() {", 95 " return \"\";", 96 " }", 97 "}"); 98 assertAbout(javaSource()).that(moduleFile) 99 .processedWith(new ComponentProcessor()) 100 .failsToCompile() 101 .withErrorContaining(formatErrorMessage(BINDING_METHOD_PRIVATE)); 102 } 103 104 @Test public void producesMethodReturnVoid() { 105 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 106 "package test;", 107 "", 108 "import dagger.producers.ProducerModule;", 109 "import dagger.producers.Produces;", 110 "", 111 "@ProducerModule", 112 "final class TestModule {", 113 " @Produces void produceNothing() {}", 114 "}"); 115 assertAbout(javaSource()).that(moduleFile) 116 .processedWith(new ComponentProcessor()) 117 .failsToCompile() 118 .withErrorContaining(formatErrorMessage(BINDING_METHOD_MUST_RETURN_A_VALUE)); 119 } 120 121 @Test public void producesMethodReturnRawFuture() { 122 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 123 "package test;", 124 "", 125 "import com.google.common.util.concurrent.ListenableFuture;", 126 "import dagger.producers.ProducerModule;", 127 "import dagger.producers.Produces;", 128 "", 129 "@ProducerModule", 130 "final class TestModule {", 131 " @Produces ListenableFuture produceRaw() {}", 132 "}"); 133 assertAbout(javaSource()).that(moduleFile) 134 .processedWith(new ComponentProcessor()) 135 .failsToCompile() 136 .withErrorContaining(PRODUCES_METHOD_RAW_FUTURE); 137 } 138 139 @Test public void producesMethodReturnWildcardFuture() { 140 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 141 "package test;", 142 "", 143 "import com.google.common.util.concurrent.ListenableFuture;", 144 "import dagger.producers.ProducerModule;", 145 "import dagger.producers.Produces;", 146 "", 147 "@ProducerModule", 148 "final class TestModule {", 149 " @Produces ListenableFuture<?> produceRaw() {}", 150 "}"); 151 assertAbout(javaSource()).that(moduleFile) 152 .processedWith(new ComponentProcessor()) 153 .failsToCompile() 154 .withErrorContaining(PRODUCES_METHOD_RETURN_TYPE); 155 } 156 157 @Test public void producesMethodWithTypeParameter() { 158 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 159 "package test;", 160 "", 161 "import dagger.producers.ProducerModule;", 162 "import dagger.producers.Produces;", 163 "", 164 "@ProducerModule", 165 "final class TestModule {", 166 " @Produces <T> String produceString() {", 167 " return \"\";", 168 " }", 169 "}"); 170 assertAbout(javaSource()).that(moduleFile) 171 .processedWith(new ComponentProcessor()) 172 .failsToCompile() 173 .withErrorContaining(formatErrorMessage(BINDING_METHOD_TYPE_PARAMETER)); 174 } 175 176 @Test public void producesMethodSetValuesWildcard() { 177 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 178 "package test;", 179 "", 180 "import static dagger.producers.Produces.Type.SET_VALUES;", 181 "", 182 "import dagger.producers.ProducerModule;", 183 "import dagger.producers.Produces;", 184 "", 185 "import java.util.Set;", 186 "", 187 "@ProducerModule", 188 "final class TestModule {", 189 " @Produces(type = SET_VALUES) Set<?> produceWildcard() {", 190 " return null;", 191 " }", 192 "}"); 193 assertAbout(javaSource()).that(moduleFile) 194 .processedWith(new ComponentProcessor()) 195 .failsToCompile() 196 .withErrorContaining(PRODUCES_METHOD_RETURN_TYPE); 197 } 198 199 @Test public void producesMethodSetValuesRawSet() { 200 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 201 "package test;", 202 "", 203 "import static dagger.producers.Produces.Type.SET_VALUES;", 204 "", 205 "import dagger.producers.ProducerModule;", 206 "import dagger.producers.Produces;", 207 "", 208 "import java.util.Set;", 209 "", 210 "@ProducerModule", 211 "final class TestModule {", 212 " @Produces(type = SET_VALUES) Set produceSomething() {", 213 " return null;", 214 " }", 215 "}"); 216 assertAbout(javaSource()).that(moduleFile) 217 .processedWith(new ComponentProcessor()) 218 .failsToCompile() 219 .withErrorContaining(formatErrorMessage(BINDING_METHOD_SET_VALUES_RAW_SET)); 220 } 221 222 @Test public void producesMethodSetValuesNotASet() { 223 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 224 "package test;", 225 "", 226 "import static dagger.producers.Produces.Type.SET_VALUES;", 227 "", 228 "import dagger.producers.ProducerModule;", 229 "import dagger.producers.Produces;", 230 "", 231 "import java.util.List;", 232 "", 233 "@ProducerModule", 234 "final class TestModule {", 235 " @Produces(type = SET_VALUES) List<String> produceStrings() {", 236 " return null;", 237 " }", 238 "}"); 239 assertAbout(javaSource()).that(moduleFile) 240 .processedWith(new ComponentProcessor()) 241 .failsToCompile() 242 .withErrorContaining(PRODUCES_METHOD_SET_VALUES_RETURN_SET); 243 } 244 245 @Test public void producesMethodSetValuesWildcardInFuture() { 246 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 247 "package test;", 248 "", 249 "import static dagger.producers.Produces.Type.SET_VALUES;", 250 "", 251 "import com.google.common.util.concurrent.ListenableFuture;", 252 "import dagger.producers.ProducerModule;", 253 "import dagger.producers.Produces;", 254 "", 255 "import java.util.Set;", 256 "", 257 "@ProducerModule", 258 "final class TestModule {", 259 " @Produces(type = SET_VALUES) ListenableFuture<Set<?>> produceWildcard() {", 260 " return null;", 261 " }", 262 "}"); 263 assertAbout(javaSource()).that(moduleFile) 264 .processedWith(new ComponentProcessor()) 265 .failsToCompile() 266 .withErrorContaining(PRODUCES_METHOD_RETURN_TYPE); 267 } 268 269 @Test public void producesMethodSetValuesFutureRawSet() { 270 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 271 "package test;", 272 "", 273 "import static dagger.producers.Produces.Type.SET_VALUES;", 274 "", 275 "import com.google.common.util.concurrent.ListenableFuture;", 276 "import dagger.producers.ProducerModule;", 277 "import dagger.producers.Produces;", 278 "", 279 "import java.util.Set;", 280 "", 281 "@ProducerModule", 282 "final class TestModule {", 283 " @Produces(type = SET_VALUES) ListenableFuture<Set> produceSomething() {", 284 " return null;", 285 " }", 286 "}"); 287 assertAbout(javaSource()).that(moduleFile) 288 .processedWith(new ComponentProcessor()) 289 .failsToCompile() 290 .withErrorContaining(formatErrorMessage(BINDING_METHOD_SET_VALUES_RAW_SET)); 291 } 292 293 @Test public void producesMethodSetValuesFutureNotASet() { 294 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 295 "package test;", 296 "", 297 "import static dagger.producers.Produces.Type.SET_VALUES;", 298 "", 299 "import com.google.common.util.concurrent.ListenableFuture;", 300 "import dagger.producers.ProducerModule;", 301 "import dagger.producers.Produces;", 302 "", 303 "import java.util.List;", 304 "", 305 "@ProducerModule", 306 "final class TestModule {", 307 " @Produces(type = SET_VALUES) ListenableFuture<List<String>> produceStrings() {", 308 " return null;", 309 " }", 310 "}"); 311 assertAbout(javaSource()).that(moduleFile) 312 .processedWith(new ComponentProcessor()) 313 .failsToCompile() 314 .withErrorContaining(PRODUCES_METHOD_SET_VALUES_RETURN_SET); 315 } 316 317 @Test public void multipleProducesMethodsWithSameName() { 318 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 319 "package test;", 320 "", 321 "import dagger.producers.ProducerModule;", 322 "import dagger.producers.Produces;", 323 "", 324 "@ProducerModule", 325 "final class TestModule {", 326 " @Produces Object produce(int i) {", 327 " return i;", 328 " }", 329 "", 330 " @Produces String produce() {", 331 " return \"\";", 332 " }", 333 "}"); 334 String errorMessage = String.format(BINDING_METHOD_WITH_SAME_NAME, "Produces"); 335 assertAbout(javaSource()).that(moduleFile) 336 .processedWith(new ComponentProcessor()) 337 .failsToCompile() 338 .withErrorContaining(errorMessage).in(moduleFile).onLine(8) 339 .and().withErrorContaining(errorMessage).in(moduleFile).onLine(12); 340 } 341 342 @Test 343 public void privateModule() { 344 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.Enclosing", 345 "package test;", 346 "", 347 "import dagger.producers.ProducerModule;", 348 "", 349 "final class Enclosing {", 350 " @ProducerModule private static final class PrivateModule {", 351 " }", 352 "}"); 353 assertAbout(javaSource()) 354 .that(moduleFile) 355 .processedWith(new ComponentProcessor()) 356 .failsToCompile() 357 .withErrorContaining("Modules cannot be private.") 358 .in(moduleFile).onLine(6); 359 } 360 361 @Test 362 public void enclosedInPrivateModule() { 363 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.Enclosing", 364 "package test;", 365 "", 366 "import dagger.producers.ProducerModule;", 367 "", 368 "final class Enclosing {", 369 " private static final class PrivateEnclosing {", 370 " @ProducerModule static final class TestModule {", 371 " }", 372 " }", 373 "}"); 374 assertAbout(javaSource()) 375 .that(moduleFile) 376 .processedWith(new ComponentProcessor()) 377 .failsToCompile() 378 .withErrorContaining("Modules cannot be enclosed in private types.") 379 .in(moduleFile).onLine(7); 380 } 381 382 @Test 383 public void includesNonModule() { 384 JavaFileObject xFile = 385 JavaFileObjects.forSourceLines("test.X", "package test;", "", "public final class X {}"); 386 JavaFileObject moduleFile = 387 JavaFileObjects.forSourceLines( 388 "test.FooModule", 389 "package test;", 390 "", 391 "import dagger.producers.ProducerModule;", 392 "", 393 "@ProducerModule(includes = X.class)", 394 "public final class FooModule {", 395 "}"); 396 assertAbout(javaSources()) 397 .that(ImmutableList.of(xFile, moduleFile)) 398 .processedWith(new ComponentProcessor()) 399 .failsToCompile() 400 .withErrorContaining( 401 String.format( 402 ErrorMessages.REFERENCED_MODULE_NOT_ANNOTATED, 403 "X", 404 "one of @Module, @ProducerModule")); 405 } 406 407 @Test 408 public void publicModuleNonPublicIncludes() { 409 JavaFileObject publicModuleFile = JavaFileObjects.forSourceLines("test.PublicModule", 410 "package test;", 411 "", 412 "import dagger.producers.ProducerModule;", 413 "", 414 "@ProducerModule(includes = {", 415 " NonPublicModule1.class, OtherPublicModule.class, NonPublicModule2.class", 416 "})", 417 "public final class PublicModule {", 418 "}"); 419 JavaFileObject nonPublicModule1File = JavaFileObjects.forSourceLines("test.NonPublicModule1", 420 "package test;", 421 "", 422 "import dagger.producers.ProducerModule;", 423 "", 424 "@ProducerModule", 425 "final class NonPublicModule1 {", 426 "}"); 427 JavaFileObject nonPublicModule2File = JavaFileObjects.forSourceLines("test.NonPublicModule2", 428 "package test;", 429 "", 430 "import dagger.producers.ProducerModule;", 431 "", 432 "@ProducerModule", 433 "final class NonPublicModule2 {", 434 "}"); 435 JavaFileObject otherPublicModuleFile = JavaFileObjects.forSourceLines("test.OtherPublicModule", 436 "package test;", 437 "", 438 "import dagger.producers.ProducerModule;", 439 "", 440 "@ProducerModule", 441 "public final class OtherPublicModule {", 442 "}"); 443 assertAbout(javaSources()) 444 .that(ImmutableList.of( 445 publicModuleFile, nonPublicModule1File, nonPublicModule2File, otherPublicModuleFile)) 446 .processedWith(new ComponentProcessor()) 447 .failsToCompile() 448 .withErrorContaining("This module is public, but it includes non-public " 449 + "(or effectively non-public) modules. " 450 + "Either reduce the visibility of this module or make " 451 + "test.NonPublicModule1 and test.NonPublicModule2 public.") 452 .in(publicModuleFile).onLine(8); 453 } 454 455 @Test public void singleProducesMethodNoArgsFuture() { 456 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 457 "package test;", 458 "", 459 "import com.google.common.util.concurrent.ListenableFuture;", 460 "import dagger.producers.ProducerModule;", 461 "import dagger.producers.Produces;", 462 "", 463 "@ProducerModule", 464 "final class TestModule {", 465 " @Produces ListenableFuture<String> produceString() {", 466 " return null;", 467 " }", 468 "}"); 469 JavaFileObject factoryFile = 470 JavaFileObjects.forSourceLines( 471 "TestModule_ProduceStringFactory", 472 "package test;", 473 "", 474 "import com.google.common.util.concurrent.AsyncFunction;", 475 "import com.google.common.util.concurrent.Futures;", 476 "import com.google.common.util.concurrent.ListenableFuture;", 477 "import dagger.producers.internal.AbstractProducer;", 478 "import dagger.producers.monitoring.ProducerMonitor;", 479 "import dagger.producers.monitoring.ProducerToken;", 480 "import dagger.producers.monitoring.ProductionComponentMonitor;", 481 "import java.util.concurrent.Executor;", 482 "import javax.annotation.Generated;", 483 "import javax.inject.Provider;", 484 "", 485 "@Generated(\"dagger.internal.codegen.ComponentProcessor\")", 486 "public final class TestModule_ProduceStringFactory extends AbstractProducer<String> {", 487 " private final TestModule module;", 488 " private final Executor executor;", 489 " private final Provider<ProductionComponentMonitor> monitorProvider;", 490 "", 491 " public TestModule_ProduceStringFactory(", 492 " TestModule module,", 493 " Executor executor,", 494 " Provider<ProductionComponentMonitor> monitorProvider) {", 495 " super(", 496 " monitorProvider,", 497 " ProducerToken.create(TestModule_ProduceStringFactory.class));", 498 " assert module != null;", 499 " this.module = module;", 500 " assert executor != null;", 501 " this.executor = executor;", 502 " assert monitorProvider != null;", 503 " this.monitorProvider = monitorProvider;", 504 " }", 505 "", 506 " @Override protected ListenableFuture<String> compute(", 507 " final ProducerMonitor monitor) {", 508 " return Futures.transform(", 509 " Futures.<Void>immediateFuture(null),", 510 " new AsyncFunction<Void, String>() {", 511 " @Override public ListenableFuture<String> apply(Void ignoredVoidArg) {", 512 " monitor.methodStarting();", 513 " try {", 514 " return module.produceString();", 515 " } finally {", 516 " monitor.methodFinished();", 517 " }", 518 " }", 519 " }, executor);", 520 " }", 521 "}"); 522 assertAbout(javaSource()) 523 .that(moduleFile) 524 .processedWith(new ComponentProcessor()) 525 .compilesWithoutError() 526 .and() 527 .generatesSources(factoryFile); 528 } 529 530 private static final JavaFileObject QUALIFIER_A = 531 JavaFileObjects.forSourceLines("test.QualifierA", 532 "package test;", 533 "", 534 "import javax.inject.Qualifier;", 535 "", 536 "@Qualifier @interface QualifierA {}"); 537 private static final JavaFileObject QUALIFIER_B = 538 JavaFileObjects.forSourceLines("test.QualifierB", 539 "package test;", 540 "", 541 "import javax.inject.Qualifier;", 542 "", 543 "@Qualifier @interface QualifierB {}"); 544 545 @Test public void producesMethodMultipleQualifiers() { 546 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 547 "package test;", 548 "", 549 "import dagger.producers.ProducerModule;", 550 "import dagger.producers.Produces;", 551 "", 552 "@ProducerModule", 553 "final class TestModule {", 554 " @Produces @QualifierA @QualifierB abstract String produceString() {", 555 " return \"\";", 556 " }", 557 "}"); 558 assertAbout(javaSources()).that(ImmutableList.of(moduleFile, QUALIFIER_A, QUALIFIER_B)) 559 .processedWith(new ComponentProcessor()) 560 .failsToCompile() 561 .withErrorContaining(PROVIDES_OR_PRODUCES_METHOD_MULTIPLE_QUALIFIERS); 562 } 563 } 564