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 // TODO(beder): Merge the error-handling tests with the ModuleFactoryGeneratorTest. 18 package dagger.internal.codegen; 19 20 import static com.google.common.truth.Truth.assertAbout; 21 import static com.google.testing.compile.CompilationSubject.assertThat; 22 import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; 23 import static dagger.internal.codegen.Compilers.daggerCompiler; 24 import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatMethodInUnannotatedClass; 25 import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatProductionModuleMethod; 26 import static java.lang.annotation.RetentionPolicy.RUNTIME; 27 28 import com.google.common.util.concurrent.ListenableFuture; 29 import com.google.testing.compile.Compilation; 30 import com.google.testing.compile.JavaFileObjects; 31 import java.lang.annotation.Retention; 32 import javax.inject.Qualifier; 33 import javax.tools.JavaFileObject; 34 import org.junit.Test; 35 import org.junit.runner.RunWith; 36 import org.junit.runners.JUnit4; 37 38 @RunWith(JUnit4.class) 39 public class ProducerModuleFactoryGeneratorTest { 40 producesMethodNotInModule()41 @Test public void producesMethodNotInModule() { 42 assertThatMethodInUnannotatedClass("@Produces String produceString() { return null; }") 43 .hasError("@Produces methods can only be present within a @ProducerModule"); 44 } 45 producesMethodAbstract()46 @Test public void producesMethodAbstract() { 47 assertThatProductionModuleMethod("@Produces abstract String produceString();") 48 .hasError("@Produces methods cannot be abstract"); 49 } 50 producesMethodPrivate()51 @Test public void producesMethodPrivate() { 52 assertThatProductionModuleMethod("@Produces private String produceString() { return null; }") 53 .hasError("@Produces methods cannot be private"); 54 } 55 producesMethodReturnVoid()56 @Test public void producesMethodReturnVoid() { 57 assertThatProductionModuleMethod("@Produces void produceNothing() {}") 58 .hasError("@Produces methods must return a value (not void)"); 59 } 60 61 @Test producesProvider()62 public void producesProvider() { 63 assertThatProductionModuleMethod("@Produces Provider<String> produceProvider() {}") 64 .hasError("@Produces methods must not return framework types"); 65 } 66 67 @Test producesLazy()68 public void producesLazy() { 69 assertThatProductionModuleMethod("@Produces Lazy<String> produceLazy() {}") 70 .hasError("@Produces methods must not return framework types"); 71 } 72 73 @Test producesMembersInjector()74 public void producesMembersInjector() { 75 assertThatProductionModuleMethod( 76 "@Produces MembersInjector<String> produceMembersInjector() {}") 77 .hasError("@Produces methods must not return framework types"); 78 } 79 80 @Test producesProducer()81 public void producesProducer() { 82 assertThatProductionModuleMethod("@Produces Producer<String> produceProducer() {}") 83 .hasError("@Produces methods must not return framework types"); 84 } 85 86 @Test producesProduced()87 public void producesProduced() { 88 assertThatProductionModuleMethod("@Produces Produced<String> produceProduced() {}") 89 .hasError("@Produces methods must not return framework types"); 90 } 91 producesMethodReturnRawFuture()92 @Test public void producesMethodReturnRawFuture() { 93 assertThatProductionModuleMethod("@Produces ListenableFuture produceRaw() {}") 94 .importing(ListenableFuture.class) 95 .hasError("@Produces methods cannot return a raw ListenableFuture"); 96 } 97 producesMethodReturnWildcardFuture()98 @Test public void producesMethodReturnWildcardFuture() { 99 assertThatProductionModuleMethod("@Produces ListenableFuture<?> produceRaw() {}") 100 .importing(ListenableFuture.class) 101 .hasError( 102 "@Produces methods can return only a primitive, an array, a type variable, " 103 + "a declared type, or a ListenableFuture of one of those types"); 104 } 105 producesMethodWithTypeParameter()106 @Test public void producesMethodWithTypeParameter() { 107 assertThatProductionModuleMethod("@Produces <T> String produceString() { return null; }") 108 .hasError("@Produces methods may not have type parameters"); 109 } 110 producesMethodSetValuesWildcard()111 @Test public void producesMethodSetValuesWildcard() { 112 assertThatProductionModuleMethod( 113 "@Produces @ElementsIntoSet Set<?> produceWildcard() { return null; }") 114 .hasError( 115 "@Produces methods can return only a primitive, an array, a type variable, " 116 + "a declared type, or a ListenableFuture of one of those types"); 117 } 118 producesMethodSetValuesRawSet()119 @Test public void producesMethodSetValuesRawSet() { 120 assertThatProductionModuleMethod( 121 "@Produces @ElementsIntoSet Set produceSomething() { return null; }") 122 .hasError("@Produces methods annotated with @ElementsIntoSet cannot return a raw Set"); 123 } 124 producesMethodSetValuesNotASet()125 @Test public void producesMethodSetValuesNotASet() { 126 assertThatProductionModuleMethod( 127 "@Produces @ElementsIntoSet List<String> produceStrings() { return null; }") 128 .hasError( 129 "@Produces methods of type set values must return a Set or ListenableFuture of Set"); 130 } 131 producesMethodSetValuesWildcardInFuture()132 @Test public void producesMethodSetValuesWildcardInFuture() { 133 assertThatProductionModuleMethod( 134 "@Produces @ElementsIntoSet " 135 + "ListenableFuture<Set<?>> produceWildcard() { return null; }") 136 .importing(ListenableFuture.class) 137 .hasError( 138 "@Produces methods can return only a primitive, an array, a type variable, " 139 + "a declared type, or a ListenableFuture of one of those types"); 140 } 141 producesMethodSetValuesFutureRawSet()142 @Test public void producesMethodSetValuesFutureRawSet() { 143 assertThatProductionModuleMethod( 144 "@Produces @ElementsIntoSet ListenableFuture<Set> produceSomething() { return null; }") 145 .importing(ListenableFuture.class) 146 .hasError("@Produces methods annotated with @ElementsIntoSet cannot return a raw Set"); 147 } 148 producesMethodSetValuesFutureNotASet()149 @Test public void producesMethodSetValuesFutureNotASet() { 150 assertThatProductionModuleMethod( 151 "@Produces @ElementsIntoSet " 152 + "ListenableFuture<List<String>> produceStrings() { return null; }") 153 .importing(ListenableFuture.class) 154 .hasError( 155 "@Produces methods of type set values must return a Set or ListenableFuture of Set"); 156 } 157 multipleProducesMethodsWithSameName()158 @Test public void multipleProducesMethodsWithSameName() { 159 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 160 "package test;", 161 "", 162 "import dagger.producers.ProducerModule;", 163 "import dagger.producers.Produces;", 164 "", 165 "@ProducerModule", 166 "final class TestModule {", 167 " @Produces Object produce(int i) {", 168 " return i;", 169 " }", 170 "", 171 " @Produces String produce() {", 172 " return \"\";", 173 " }", 174 "}"); 175 String errorMessage = 176 "Cannot have more than one binding method with the same name in a single module"; 177 Compilation compilation = daggerCompiler().compile(moduleFile); 178 assertThat(compilation).failed(); 179 assertThat(compilation).hadErrorContaining(errorMessage).inFile(moduleFile).onLine(8); 180 assertThat(compilation).hadErrorContaining(errorMessage).inFile(moduleFile).onLine(12); 181 } 182 183 @Test producesMethodThrowsThrowable()184 public void producesMethodThrowsThrowable() { 185 assertThatProductionModuleMethod("@Produces int produceInt() throws Throwable { return 0; }") 186 .hasError( 187 "@Produces methods may only throw unchecked exceptions or exceptions subclassing " 188 + "Exception"); 189 } 190 producesMethodWithScope()191 @Test public void producesMethodWithScope() { 192 assertThatProductionModuleMethod("@Produces @Singleton String str() { return \"\"; }") 193 .hasError("@Produces methods cannot be scoped"); 194 } 195 196 @Test privateModule()197 public void privateModule() { 198 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.Enclosing", 199 "package test;", 200 "", 201 "import dagger.producers.ProducerModule;", 202 "", 203 "final class Enclosing {", 204 " @ProducerModule private static final class PrivateModule {", 205 " }", 206 "}"); 207 Compilation compilation = daggerCompiler().compile(moduleFile); 208 assertThat(compilation).failed(); 209 assertThat(compilation) 210 .hadErrorContaining("Modules cannot be private") 211 .inFile(moduleFile) 212 .onLine(6); 213 } 214 215 216 @Test enclosedInPrivateModule()217 public void enclosedInPrivateModule() { 218 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.Enclosing", 219 "package test;", 220 "", 221 "import dagger.producers.ProducerModule;", 222 "", 223 "final class Enclosing {", 224 " private static final class PrivateEnclosing {", 225 " @ProducerModule static final class TestModule {", 226 " }", 227 " }", 228 "}"); 229 Compilation compilation = daggerCompiler().compile(moduleFile); 230 assertThat(compilation).failed(); 231 assertThat(compilation) 232 .hadErrorContaining("Modules cannot be enclosed in private types") 233 .inFile(moduleFile) 234 .onLine(7); 235 } 236 237 @Test includesNonModule()238 public void includesNonModule() { 239 JavaFileObject xFile = 240 JavaFileObjects.forSourceLines("test.X", "package test;", "", "public final class X {}"); 241 JavaFileObject moduleFile = 242 JavaFileObjects.forSourceLines( 243 "test.FooModule", 244 "package test;", 245 "", 246 "import dagger.producers.ProducerModule;", 247 "", 248 "@ProducerModule(includes = X.class)", 249 "public final class FooModule {", 250 "}"); 251 Compilation compilation = daggerCompiler().compile(xFile, moduleFile); 252 assertThat(compilation).failed(); 253 assertThat(compilation) 254 .hadErrorContaining( 255 "X is listed as a module, but is not annotated with one of @Module, @ProducerModule"); 256 } 257 258 // TODO(ronshapiro): merge this with the equivalent test in ModuleFactoryGeneratorTest and make it 259 // parameterized 260 @Test publicModuleNonPublicIncludes()261 public void publicModuleNonPublicIncludes() { 262 JavaFileObject publicModuleFile = JavaFileObjects.forSourceLines("test.PublicModule", 263 "package test;", 264 "", 265 "import dagger.producers.ProducerModule;", 266 "", 267 "@ProducerModule(includes = {", 268 " BadNonPublicModule.class, OtherPublicModule.class, OkNonPublicModule.class", 269 "})", 270 "public final class PublicModule {", 271 "}"); 272 JavaFileObject badNonPublicModuleFile = 273 JavaFileObjects.forSourceLines( 274 "test.BadNonPublicModule", 275 "package test;", 276 "", 277 "import dagger.producers.ProducerModule;", 278 "import dagger.producers.Produces;", 279 "", 280 "@ProducerModule", 281 "final class BadNonPublicModule {", 282 " @Produces", 283 " int produceInt() {", 284 " return 42;", 285 " }", 286 "}"); 287 JavaFileObject okNonPublicModuleFile = JavaFileObjects.forSourceLines("test.OkNonPublicModule", 288 "package test;", 289 "", 290 "import dagger.producers.ProducerModule;", 291 "import dagger.producers.Produces;", 292 "", 293 "@ProducerModule", 294 "final class OkNonPublicModule {", 295 " @Produces", 296 " static String produceString() {", 297 " return \"foo\";", 298 " }", 299 "}"); 300 JavaFileObject otherPublicModuleFile = JavaFileObjects.forSourceLines("test.OtherPublicModule", 301 "package test;", 302 "", 303 "import dagger.producers.ProducerModule;", 304 "", 305 "@ProducerModule", 306 "public final class OtherPublicModule {", 307 "}"); 308 Compilation compilation = 309 daggerCompiler() 310 .compile( 311 publicModuleFile, 312 badNonPublicModuleFile, 313 okNonPublicModuleFile, 314 otherPublicModuleFile); 315 assertThat(compilation).failed(); 316 assertThat(compilation) 317 .hadErrorContaining( 318 "This module is public, but it includes non-public (or effectively non-public) modules " 319 + "(test.BadNonPublicModule) that have non-static, non-abstract binding methods. " 320 + "Either reduce the visibility of this module, make the included modules public, " 321 + "or make all of the binding methods on the included modules abstract or static.") 322 .inFile(publicModuleFile) 323 .onLine(8); 324 } 325 argumentNamedModuleCompiles()326 @Test public void argumentNamedModuleCompiles() { 327 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 328 "package test;", 329 "", 330 "import dagger.producers.ProducerModule;", 331 "import dagger.producers.Produces;", 332 "", 333 "@ProducerModule", 334 "final class TestModule {", 335 " @Produces String produceString(int module) {", 336 " return null;", 337 " }", 338 "}"); 339 Compilation compilation = daggerCompiler().compile(moduleFile); 340 assertThat(compilation).succeeded(); 341 } 342 singleProducesMethodNoArgsFuture()343 @Test public void singleProducesMethodNoArgsFuture() { 344 JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", 345 "package test;", 346 "", 347 "import com.google.common.util.concurrent.ListenableFuture;", 348 "import dagger.producers.ProducerModule;", 349 "import dagger.producers.Produces;", 350 "", 351 "@ProducerModule", 352 "final class TestModule {", 353 " @Produces ListenableFuture<String> produceString() {", 354 " return null;", 355 " }", 356 "}"); 357 JavaFileObject factoryFile = 358 JavaFileObjects.forSourceLines( 359 "TestModule_ProduceStringFactory", 360 "package test;", 361 "", 362 GeneratedLines.generatedImports( 363 "import com.google.common.util.concurrent.Futures;", 364 "import com.google.common.util.concurrent.ListenableFuture;", 365 "import dagger.producers.internal.AbstractProducesMethodProducer;", 366 "import dagger.producers.monitoring.ProducerToken;", 367 "import dagger.producers.monitoring.ProductionComponentMonitor;", 368 "import java.util.concurrent.Executor;", 369 "import javax.inject.Provider;"), 370 "", 371 GeneratedLines.generatedAnnotationsWithoutSuppressWarnings(), 372 "@SuppressWarnings({\"FutureReturnValueIgnored\", \"unchecked\", \"rawtypes\"})", 373 "public final class TestModule_ProduceStringFactory", 374 " extends AbstractProducesMethodProducer<Void, String> {", 375 " private final TestModule module;", 376 "", 377 " private TestModule_ProduceStringFactory(", 378 " TestModule module,", 379 " Provider<Executor> executorProvider,", 380 " Provider<ProductionComponentMonitor> productionComponentMonitorProvider) {", 381 " super(", 382 " productionComponentMonitorProvider,", 383 " ProducerToken.create(TestModule_ProduceStringFactory.class),", 384 " executorProvider);", 385 " this.module = module;", 386 " }", 387 "", 388 " public static TestModule_ProduceStringFactory create(", 389 " TestModule module,", 390 " Provider<Executor> executorProvider,", 391 " Provider<ProductionComponentMonitor> productionComponentMonitorProvider) {", 392 " return new TestModule_ProduceStringFactory(", 393 " module, executorProvider, productionComponentMonitorProvider);", 394 " }", 395 "", 396 " @Override protected ListenableFuture<Void> collectDependencies() {", 397 " return Futures.<Void>immediateFuture(null);", 398 " }", 399 "", 400 " @Override public ListenableFuture<String> callProducesMethod(Void ignoredVoidArg) {", 401 " return module.produceString();", 402 " }", 403 "}"); 404 assertAbout(javaSource()) 405 .that(moduleFile) 406 .processedWith(new ComponentProcessor()) 407 .compilesWithoutError() 408 .and() 409 .generatesSources(factoryFile); 410 } 411 412 @Test singleProducesMethodNoArgsFutureWithProducerName()413 public void singleProducesMethodNoArgsFutureWithProducerName() { 414 JavaFileObject moduleFile = 415 JavaFileObjects.forSourceLines( 416 "test.TestModule", 417 "package test;", 418 "", 419 "import com.google.common.util.concurrent.Futures;", 420 "import com.google.common.util.concurrent.ListenableFuture;", 421 "import dagger.producers.ProducerModule;", 422 "import dagger.producers.Produces;", 423 "", 424 "@ProducerModule", 425 "final class TestModule {", 426 " @Produces ListenableFuture<String> produceString() {", 427 " return Futures.immediateFuture(\"\");", 428 " }", 429 "}"); 430 JavaFileObject factoryFile = 431 JavaFileObjects.forSourceLines( 432 "TestModule_ProduceStringFactory", 433 "package test;", 434 "", 435 GeneratedLines.generatedImports( 436 "import com.google.common.util.concurrent.Futures;", 437 "import com.google.common.util.concurrent.ListenableFuture;", 438 "import dagger.producers.internal.AbstractProducesMethodProducer;", 439 "import dagger.producers.monitoring.ProducerToken;", 440 "import dagger.producers.monitoring.ProductionComponentMonitor;", 441 "import java.util.concurrent.Executor;", 442 "import javax.inject.Provider;"), 443 "", 444 GeneratedLines.generatedAnnotationsWithoutSuppressWarnings(), 445 "@SuppressWarnings({\"FutureReturnValueIgnored\", \"unchecked\", \"rawtypes\"})", 446 "public final class TestModule_ProduceStringFactory", 447 " extends AbstractProducesMethodProducer<Void, String> {", 448 " private final TestModule module;", 449 "", 450 " private TestModule_ProduceStringFactory(", 451 " TestModule module,", 452 " Provider<Executor> executorProvider,", 453 " Provider<ProductionComponentMonitor> productionComponentMonitorProvider) {", 454 " super(", 455 " productionComponentMonitorProvider,", 456 " ProducerToken.create(\"test.TestModule#produceString\"),", 457 " executorProvider);", 458 " this.module = module;", 459 " }", 460 "", 461 " public static TestModule_ProduceStringFactory create(", 462 " TestModule module,", 463 " Provider<Executor> executorProvider,", 464 " Provider<ProductionComponentMonitor> productionComponentMonitorProvider) {", 465 " return new TestModule_ProduceStringFactory(", 466 " module, executorProvider, productionComponentMonitorProvider);", 467 " }", 468 "", 469 " @Override protected ListenableFuture<Void> collectDependencies() {", 470 " return Futures.<Void>immediateFuture(null);", 471 " }", 472 "", 473 " @Override public ListenableFuture<String> callProducesMethod(Void ignoredVoidArg) {", 474 " return module.produceString();", 475 " }", 476 "}"); 477 assertAbout(javaSource()) 478 .that(moduleFile) 479 .withCompilerOptions("-Adagger.writeProducerNameInToken=ENABLED") 480 .processedWith(new ComponentProcessor()) 481 .compilesWithoutError() 482 .and() 483 .generatesSources(factoryFile); 484 } 485 486 @Test producesMethodMultipleQualifiersOnMethod()487 public void producesMethodMultipleQualifiersOnMethod() { 488 assertThatProductionModuleMethod( 489 "@Produces @QualifierA @QualifierB static String produceString() { return null; }") 490 .importing(ListenableFuture.class, QualifierA.class, QualifierB.class) 491 .hasError("may not use more than one @Qualifier"); 492 } 493 494 @Test producesMethodMultipleQualifiersOnParameter()495 public void producesMethodMultipleQualifiersOnParameter() { 496 assertThatProductionModuleMethod( 497 "@Produces static String produceString(@QualifierA @QualifierB Object input) " 498 + "{ return null; }") 499 .importing(ListenableFuture.class, QualifierA.class, QualifierB.class) 500 .hasError("may not use more than one @Qualifier"); 501 } 502 503 @Test producesMethodWildcardDependency()504 public void producesMethodWildcardDependency() { 505 assertThatProductionModuleMethod( 506 "@Produces static String produceString(Provider<? extends Number> numberProvider) " 507 + "{ return null; }") 508 .importing(ListenableFuture.class, QualifierA.class, QualifierB.class) 509 .hasError( 510 "Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, or Produced<T> " 511 + "when T is a wildcard type such as ? extends java.lang.Number"); 512 } 513 514 @Qualifier 515 @Retention(RUNTIME) 516 public @interface QualifierA {} 517 518 @Qualifier 519 @Retention(RUNTIME) 520 public @interface QualifierB {} 521 } 522