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